diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 00000000..94c20889 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,35 @@ +--- +name: Firebase C++ Sample issue +about: Report issues with the Firebase C++ Sample. +labels: new +--- + + +### [READ] For Firebase C++ SDK issues, please report to [Firebase C++ open-source](https://github.com/firebase/firebase-cpp-sdk/issues/new/choose) + +Once you've read this section and determined that your issue is appropriate for this repository, please delete this section. + +### [REQUIRED] Please fill in the following fields: + + * Which Firebase Sample: _____ (Auth, Database, etc.) + * Firebase C++ SDK version: _____ + * Additional SDKs you are using: _____ (Facebook, AdMob, etc.) + * Platform you are using the SDK on: _____ (Mac, Windows, or Linux) + * Platform you are targeting: _____ (iOS, Android, and/or desktop) + +### [REQUIRED] Please describe the issue here: + +(Please list the full steps to reproduce the issue. Include device logs, and stack traces if available.) + +#### Steps to reproduce: + +What's the issue repro rate? (eg 100%, 1/5 etc) + +What happened? How can we make the problem occur? +This could be a description, log/console output, etc. + +If you have a downloadable sample project that reproduces the bug you're reporting, you will +likely receive a faster response on your issue. diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c4f78aab --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +GoogleService-Info.plist +google-services.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02f251e9..c02eddc2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,7 +109,7 @@ Before you submit your pull request consider the following guidelines: * Make your changes in a new git branch: ```shell - git checkout -b my-fix-branch master + git checkout -b my-fix-branch main ``` * Create your patch, **including appropriate test cases**. @@ -133,14 +133,14 @@ Before you submit your pull request consider the following guidelines: git push origin my-fix-branch ``` -* In GitHub, send a pull request to `firebase/quickstart-cpp:master`. +* In GitHub, send a pull request to `firebase/quickstart-cpp:main`. * If we suggest changes then: * Make the required updates. * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): ```shell - git rebase master -i + git rebase main -i git push origin my-fix-branch -f ``` @@ -158,10 +158,10 @@ the changes from the main (upstream) repository: git push origin --delete my-fix-branch ``` -* Check out the master branch: +* Check out the main branch: ```shell - git checkout master -f + git checkout main -f ``` * Delete the local branch: @@ -170,10 +170,10 @@ the changes from the main (upstream) repository: git branch -D my-fix-branch ``` -* Update your master with the latest upstream version: +* Update your main with the latest upstream version: ```shell - git pull --ff upstream master + git pull --ff upstream main ``` ## Coding Rules @@ -186,7 +186,7 @@ Please sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code changes to be accepted, the CLA must be signed. It's a quick process, we promise! -*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).* +*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/main/CONTRIBUTING.md).* [github]: https://github.com/firebase/quickstart-cpp [google-cla]: https://cla.developers.google.com diff --git a/admob/testapp/Podfile b/admob/testapp/Podfile deleted file mode 100644 index d7428213..00000000 --- a/admob/testapp/Podfile +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '7.0' -# AdMob test application. -target 'testapp' do - pod 'Firebase/AdMob' - pod 'Firebase/Core' -end \ No newline at end of file diff --git a/admob/testapp/build.gradle b/admob/testapp/build.gradle deleted file mode 100644 index 53c3a381..00000000 --- a/admob/testapp/build.gradle +++ /dev/null @@ -1,117 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - mavenLocal() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' - } -} - -allprojects { - repositories { - mavenLocal() - jcenter() - } -} - -apply plugin: 'com.android.application' - -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - -android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' - - 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 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' - } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/admob.pro") - } - } -} - -dependencies { - compile 'com.google.firebase:firebase-ads:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' -} - -apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/admob/testapp/jni/Android.mk b/admob/testapp/jni/Android.mk deleted file mode 100644 index 76ef6dc6..00000000 --- a/admob/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_admob -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libadmob.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_admob \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/admob/testapp/jni/Application.mk b/admob/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/admob/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/admob/testapp/readme.md b/admob/testapp/readme.md deleted file mode 100644 index 74300c31..00000000 --- a/admob/testapp/readme.md +++ /dev/null @@ -1,141 +0,0 @@ -Firebase AdMob Quickstart -============================== - -The Firebase AdMob Test Application (testapp) demonstrates loading and showing -banners and interstitials using the Firebase AdMob C++ SDK. The application -has no user interface and simply logs actions it's performing to the console -while displaying the ads. - -Introduction ------------- - -- [Read more about Firebase AdMob](https://firebase.google.com/docs/admob) - -Getting Started ---------------- - -### iOS - - Link your iOS app to the Firebase libraries. - - Get CocoaPods version 1 or later by running, - ``` - $ sudo gem install CocoaPods --pre - ``` - - From the testapp directory, install the CocoaPods listed in the Podfile - by running, - ``` - $ pod install - ``` - - Open the generated Xcode workspace (which now has the CocoaPods), - ``` - $ open testapp.xcworkspace - ``` - - For further details please refer to the - [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). - - Register your iOS app with Firebase. - - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach - your iOS app to it. - - You can use "com.google.ios.admob.testapp" as the iOS Bundle ID - while you're testing. You can omit App Store ID while testing. - - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - - Add the following frameworks from the Firebase C++ SDK to the project: - - frameworks/ios/universal/firebase.framework - - frameworks/ios/universal/firebase_admob.framework - - You will need to either, - 1. Check "Copy items if needed" when adding the frameworks, or - 2. Add the framework path in "Framework Search Paths" - - For example, if you downloaded the Firebase C++ SDK to - `/Users/me/firebase_cpp_sdk`, - then you would add the path - `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. - - To add the path, in Xcode, select your project in the project - navigator, then select your target in the main window. - Select the "Build Settings" tab, and click "All" to see all - the build settings. Scroll down to "Search Paths", and add - your path to "Framework Search Paths". - - In Xcode, build & run the sample on an iOS device or simulator. - - The testapp displays a banner ad and an interstitial ad. You can - dismiss the interstitial ad to see the banner ad. The output of the app can - be viewed via the console. To view the conscole in Xcode, select - "View --> Debug Area --> Activate Console" from the menu. - - For Rewarded Video, you must supply your own ad unit. Learn more in the - [Rewarded Video C++ guide](http://firebase.google.com/docs/admob/cpp/rewarded-video). - -### Android - - Register your Android app with Firebase. - - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach - your Android app to it. - - You can use "com.google.android.admob.testapp" as the Package Name - while you're testing. - - To [generate a SHA1](https://developers.google.com/android/guides/client-auth) - run this command on Mac and Linux, - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore - ``` - or this command on Windows, - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore - ``` - - If keytool reports that you do not have a debug.keystore, you can - [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), - ``` - keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" - ``` - - Add the `google-services.json` file that you downloaded from Firebase - console to the root directory of testapp. This file identifies your - Android app to the Firebase backend. - - For further details please refer to the - [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - - Configure the location of the Firebase C++ SDK by setting the - firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. - For example, in the project directory: - ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties - ``` - - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. - - Open *build.gradle* in Android Studio. - - From the Android Studio launch menu, "Open an existing Android Studio - project", and select `build.gradle`. - - Install the SDK Platforms that Android Studio reports missing. - - Build the testapp and run it on an Android device or emulator. - - The testapp will initialize AdMob, then load and display a test banner and - a test interstitial. - - Tapping on an ad to verify the clickthrough process is possible, and the - interstitial will wait to be closed by the user. - - While this is happening, information from the device log will be written - to an onscreen TextView. - - Logcat can also be used as normal. - - For Rewarded Video, you must supply your own ad unit. Learn more in the - [Rewarded Video C++ guide](http://firebase.google.com/docs/admob/cpp/rewarded-video). - -Support -------- - -https://firebase.google.com/support/ - -License -------- - -Copyright 2016 Google, Inc. - -Licensed to the Apache Software Foundation (ASF) under one or more contributor -license agreements. See the NOTICE file distributed with this work for -additional information regarding copyright ownership. The ASF licenses this -file to you 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. diff --git a/admob/testapp/src/common_main.cc b/admob/testapp/src/common_main.cc deleted file mode 100644 index abf34c61..00000000 --- a/admob/testapp/src/common_main.cc +++ /dev/null @@ -1,495 +0,0 @@ -// Copyright 2016 Google Inc. 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 "firebase/admob.h" -#include "firebase/admob/banner_view.h" -#include "firebase/admob/interstitial_ad.h" -#include "firebase/admob/native_express_ad_view.h" -#include "firebase/admob/rewarded_video.h" -#include "firebase/admob/types.h" -#include "firebase/app.h" -#include "firebase/future.h" - -// Thin OS abstraction layer. -#include "main.h" // NOLINT - -// A simple listener that logs changes to a BannerView. -class LoggingBannerViewListener : public firebase::admob::BannerView::Listener { - public: - LoggingBannerViewListener() {} - void OnPresentationStateChanged( - firebase::admob::BannerView* banner_view, - firebase::admob::BannerView::PresentationState state) override { - ::LogMessage("BannerView PresentationState has changed to %d.", state); - } - void OnBoundingBoxChanged(firebase::admob::BannerView* banner_view, - firebase::admob::BoundingBox box) override { - ::LogMessage( - "BannerView BoundingBox has changed to (x: %d, y: %d, width: %d, " - "height %d).", - box.x, box.y, box.width, box.height); - } -}; - -// A simple listener that logs changes to an InterstitialAd. -class LoggingInterstitialAdListener - : public firebase::admob::InterstitialAd::Listener { - public: - LoggingInterstitialAdListener() {} - void OnPresentationStateChanged( - firebase::admob::InterstitialAd* interstitial_ad, - firebase::admob::InterstitialAd::PresentationState state) override { - ::LogMessage("InterstitialAd PresentationState has changed to %d.", state); - } -}; - -// A simple listener that logs changes to a NativeExpressAdView. -class LoggingNativeExpressAdViewListener - : public firebase::admob::NativeExpressAdView::Listener { - public: - LoggingNativeExpressAdViewListener() {} - void OnPresentationStateChanged( - firebase::admob::NativeExpressAdView* native_express_ad_view, - firebase::admob::NativeExpressAdView::PresentationState state) override { - ::LogMessage("NativeExpressAdView PresentationState has changed to %d.", - state); - } - void OnBoundingBoxChanged( - firebase::admob::NativeExpressAdView* native_express_ad_view, - firebase::admob::BoundingBox box) override { - ::LogMessage( - "NativeExpressAdView BoundingBox has changed to (x: %d, y: %d, width: " - "%d, height %d).", - box.x, box.y, box.width, box.height); - } -}; - -// A simple listener that logs changes to rewarded video state. -class LoggingRewardedVideoListener - : public firebase::admob::rewarded_video::Listener { - public: - LoggingRewardedVideoListener() {} - void OnRewarded(firebase::admob::rewarded_video::RewardItem reward) override { - ::LogMessage("Rewarding user with %f %s.", reward.amount, - reward.reward_type.c_str()); - } - void OnPresentationStateChanged( - firebase::admob::rewarded_video::PresentationState state) override { - ::LogMessage("Rewarded video PresentationState has changed to %d.", state); - } -}; - -// The AdMob app IDs for the test app. -#if defined(__ANDROID__) -const char* kAdMobAppID = "ca-app-pub-3940256099942544~3347511713"; -#else -const char* kAdMobAppID = "ca-app-pub-3940256099942544~1458002511"; -#endif - -// These ad units are configured to always serve test ads. -#if defined(__ANDROID__) -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/6300978111"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/1033173712"; -const char* kNativeExpressAdUnit = "ca-app-pub-3940256099942544/1072772517"; -const char* kRewardedVideoAdUnit = "YOUR_REWARDED_VIDEO_AD_UNIT_ID"; -#else -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; -const char* kNativeExpressAdUnit = "ca-app-pub-3940256099942544/2562852117"; -const char* kRewardedVideoAdUnit = "YOUR_REWARDED_VIDEO_AD_UNIT_ID"; -#endif - -// Standard mobile banner size is 320x50. -static const int kBannerWidth = 320; -static const int kBannerHeight = 50; - -// The native express ad's width and height. -static const int kNativeExpressAdWidth = 320; -static const int kNativeExpressAdHeight = 220; - -// Sample keywords to use in making the request. -static const char* kKeywords[] = {"AdMob", "C++", "Fun"}; - -// Sample test device IDs to use in making the request. -static const char* kTestDeviceIDs[] = {"2077ef9a63d2b398840261c8221a0c9b", - "098fe087d987c9a878965454a65654d7"}; - -// Sample birthday value to use in making the request. -static const int kBirthdayDay = 10; -static const int kBirthdayMonth = 11; -static const int kBirthdayYear = 1976; - -static void WaitForFutureCompletion(firebase::FutureBase future) { - while (!ProcessEvents(1000)) { - if (future.status() != firebase::kFutureStatusPending) { - break; - } - } - - if (future.error() != firebase::admob::kAdMobErrorNone) { - LogMessage("ERROR: Action failed with error code %d and message \"%s\".", - future.error(), future.error_message()); - } -} - -// Execute all methods of the C++ admob API. -extern "C" int common_main(int argc, const char* argv[]) { - firebase::App* app; - LogMessage("Initializing the AdMob library."); - -#if defined(__ANDROID__) - app = ::firebase::App::Create(GetJniEnv(), GetActivity()); -#else - app = ::firebase::App::Create(); -#endif // defined(__ANDROID__) - - LogMessage("Created the Firebase App %x.", - static_cast(reinterpret_cast(app))); - - LogMessage("Initializing the AdMob with Firebase API."); - firebase::admob::Initialize(*app, kAdMobAppID); - - firebase::admob::AdRequest request; - // If the app is aware of the user's gender, it can be added to the targeting - // information. Otherwise, "unknown" should be used. - request.gender = firebase::admob::kGenderUnknown; - - // This value allows publishers to specify whether they would like the request - // to be treated as child-directed for purposes of the Children’s Online - // Privacy Protection Act (COPPA). - // See http://business.ftc.gov/privacy-and-security/childrens-privacy. - request.tagged_for_child_directed_treatment = - firebase::admob::kChildDirectedTreatmentStateTagged; - - // The user's birthday, if known. Note that months are indexed from one. - request.birthday_day = kBirthdayDay; - request.birthday_month = kBirthdayMonth; - request.birthday_year = kBirthdayYear; - - // Additional keywords to be used in targeting. - request.keyword_count = sizeof(kKeywords) / sizeof(kKeywords[0]); - request.keywords = kKeywords; - - // "Extra" key value pairs can be added to the request as well. Typically - // these are used when testing new features. - static const firebase::admob::KeyValuePair kRequestExtras[] = { - {"the_name_of_an_extra", "the_value_for_that_extra"}}; - request.extras_count = sizeof(kRequestExtras) / sizeof(kRequestExtras[0]); - request.extras = kRequestExtras; - - // 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 AdMob 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. - request.test_device_id_count = - sizeof(kTestDeviceIDs) / sizeof(kTestDeviceIDs[0]); - request.test_device_ids = kTestDeviceIDs; - - // Create an ad size for the BannerView. - firebase::admob::AdSize banner_ad_size; - banner_ad_size.ad_size_type = firebase::admob::kAdSizeStandard; - banner_ad_size.width = kBannerWidth; - banner_ad_size.height = kBannerHeight; - - LogMessage("Creating the BannerView."); - firebase::admob::BannerView* banner = new firebase::admob::BannerView(); - banner->Initialize(GetWindowContext(), kBannerAdUnit, banner_ad_size); - - WaitForFutureCompletion(banner->InitializeLastResult()); - - // Set the listener. - LoggingBannerViewListener banner_listener; - banner->SetListener(&banner_listener); - - // Load the banner ad. - LogMessage("Loading a banner ad."); - banner->LoadAd(request); - - WaitForFutureCompletion(banner->LoadAdLastResult()); - - // Make the BannerView visible. - LogMessage("Showing the banner ad."); - banner->Show(); - - WaitForFutureCompletion(banner->ShowLastResult()); - - // Move to each of the six pre-defined positions. - LogMessage("Moving the banner ad to top-center."); - banner->MoveTo(firebase::admob::BannerView::kPositionTop); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-left."); - banner->MoveTo(firebase::admob::BannerView::kPositionTopLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-right."); - banner->MoveTo(firebase::admob::BannerView::kPositionTopRight); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-center."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottom); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-left."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottomLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-right."); - banner->MoveTo(firebase::admob::BannerView::kPositionBottomRight); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - // Try some coordinate moves. - LogMessage("Moving the banner ad to (100, 300)."); - banner->MoveTo(100, 300); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to (100, 400)."); - banner->MoveTo(100, 400); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - // Try hiding and showing the BannerView. - LogMessage("Hiding the banner ad."); - banner->Hide(); - - WaitForFutureCompletion(banner->HideLastResult()); - - LogMessage("Showing the banner ad."); - banner->Show(); - - WaitForFutureCompletion(banner->ShowLastResult()); - - // A few last moves after showing it again. - LogMessage("Moving the banner ad to (100, 300)."); - banner->MoveTo(100, 300); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to (100, 400)."); - banner->MoveTo(100, 400); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Hiding the banner ad now that we're done with it."); - banner->Hide(); - - WaitForFutureCompletion(banner->HideLastResult()); - - // Create and test InterstitialAd. - LogMessage("Creating the InterstitialAd."); - firebase::admob::InterstitialAd* interstitial = - new firebase::admob::InterstitialAd(); - interstitial->Initialize(GetWindowContext(), kInterstitialAdUnit); - - WaitForFutureCompletion(interstitial->InitializeLastResult()); - - // Set the listener. - LoggingInterstitialAdListener interstitial_listener; - interstitial->SetListener(&interstitial_listener); - - // When the InterstitialAd is initialized, load an ad. - LogMessage("Loading an interstitial ad."); - interstitial->LoadAd(request); - - WaitForFutureCompletion(interstitial->LoadAdLastResult()); - - // When the InterstitialAd has loaded an ad, show it. - LogMessage("Showing the interstitial ad."); - interstitial->Show(); - - WaitForFutureCompletion(interstitial->ShowLastResult()); - - // Wait for the user to close the interstitial. - while (interstitial->presentation_state() != - firebase::admob::InterstitialAd::PresentationState:: - kPresentationStateHidden) { - ProcessEvents(1000); - } - - // Create an ad size for the NativeExpressAdView. - firebase::admob::AdSize native_express_ad_size; - native_express_ad_size.ad_size_type = firebase::admob::kAdSizeStandard; - native_express_ad_size.width = kNativeExpressAdWidth; - native_express_ad_size.height = kNativeExpressAdHeight; - - LogMessage("Creating the NativeExpressAdView."); - firebase::admob::NativeExpressAdView* native_express_ad = - new firebase::admob::NativeExpressAdView(); - native_express_ad->Initialize(GetWindowContext(), kNativeExpressAdUnit, - native_express_ad_size); - - WaitForFutureCompletion(native_express_ad->InitializeLastResult()); - - // Set the listener. - LoggingNativeExpressAdViewListener native_express_ad_listener; - native_express_ad->SetListener(&native_express_ad_listener); - - // Load the native express ad. - LogMessage("Loading a native express ad."); - native_express_ad->LoadAd(request); - - WaitForFutureCompletion(native_express_ad->LoadAdLastResult()); - - // Make the NativeExpressAdView visible. - LogMessage("Showing the native express ad."); - native_express_ad->Show(); - - WaitForFutureCompletion(native_express_ad->ShowLastResult()); - - // Move to each of the six pre-defined positions. - LogMessage("Moving the native express ad to top-center."); - native_express_ad->MoveTo(firebase::admob::NativeExpressAdView::kPositionTop); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to top-left."); - native_express_ad->MoveTo( - firebase::admob::NativeExpressAdView::kPositionTopLeft); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to top-right."); - native_express_ad->MoveTo( - firebase::admob::NativeExpressAdView::kPositionTopRight); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to bottom-center."); - native_express_ad->MoveTo( - firebase::admob::NativeExpressAdView::kPositionBottom); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to bottom-left."); - native_express_ad->MoveTo( - firebase::admob::NativeExpressAdView::kPositionBottomLeft); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to bottom-right."); - native_express_ad->MoveTo( - firebase::admob::NativeExpressAdView::kPositionBottomRight); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - // Try some coordinate moves. - LogMessage("Moving the native express ad to (100, 300)."); - native_express_ad->MoveTo(100, 300); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to (100, 400)."); - native_express_ad->MoveTo(100, 400); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - // Try hiding and showing the NativeExpressAdView. - LogMessage("Hiding the native express ad."); - native_express_ad->Hide(); - - WaitForFutureCompletion(native_express_ad->HideLastResult()); - - LogMessage("Showing the native express ad."); - native_express_ad->Show(); - - WaitForFutureCompletion(native_express_ad->ShowLastResult()); - - // A few last moves after showing it again. - LogMessage("Moving the native express ad to (100, 300)."); - native_express_ad->MoveTo(100, 300); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Moving the native express ad to (100, 400)."); - native_express_ad->MoveTo(100, 400); - - WaitForFutureCompletion(native_express_ad->MoveToLastResult()); - - LogMessage("Hiding the native express ad now that we're done with it."); - native_express_ad->Hide(); - - WaitForFutureCompletion(native_express_ad->HideLastResult()); - - // Start up rewarded video ads and associated mediation adapters. - LogMessage("Initializing rewarded video."); - namespace rewarded_video = firebase::admob::rewarded_video; - rewarded_video::Initialize(); - - WaitForFutureCompletion(rewarded_video::InitializeLastResult()); - - LogMessage("Setting rewarded video listener."); - LoggingRewardedVideoListener rewarded_listener; - rewarded_video::SetListener(&rewarded_listener); - - LogMessage("Loading a rewarded video ad."); - rewarded_video::LoadAd(kRewardedVideoAdUnit, request); - - WaitForFutureCompletion(rewarded_video::LoadAdLastResult()); - - // If an ad has loaded, show it. If the user watches all the way through, the - // LoggingRewardedVideoListener will log a reward! - if (rewarded_video::LoadAdLastResult().error() == - firebase::admob::kAdMobErrorNone) { - LogMessage("Showing a rewarded video ad."); - rewarded_video::Show(GetWindowContext()); - - WaitForFutureCompletion(rewarded_video::ShowLastResult()); - - // Normally Pause and Resume would be called in response to the app pausing - // or losing focus. This is just a test. - LogMessage("Pausing."); - rewarded_video::Pause(); - - WaitForFutureCompletion(rewarded_video::PauseLastResult()); - - LogMessage("Resuming."); - rewarded_video::Resume(); - - WaitForFutureCompletion(rewarded_video::ResumeLastResult()); - } else { - // Rewarded Video returned an error. This might be because the - // developer did not put their Rewarded Video ad unit into - // kRewardedVideoAdUnit above. - LogMessage("WARNING: Is your Rewarded Video ad unit ID correct?"); - LogMessage( - "Ensure kRewardedVideoAdUnit is set to your own Rewarded Video ad unit " - "ID in src/common_main.cc."); - } - - LogMessage("Done!"); - - // Wait until the user kills the app. - while (!ProcessEvents(1000)) { - } - - delete banner; - delete interstitial; - delete native_express_ad; - rewarded_video::Destroy(); - firebase::admob::Terminate(); - delete app; - - return 0; -} diff --git a/analytics/testapp/AndroidManifest.xml b/analytics/testapp/AndroidManifest.xml index 3a7b2043..b7038a15 100644 --- a/analytics/testapp/AndroidManifest.xml +++ b/analytics/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + ) + 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_analytics firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) + diff --git a/analytics/testapp/Podfile b/analytics/testapp/Podfile index f701ef72..5895e3a3 100644 --- a/analytics/testapp/Podfile +++ b/analytics/testapp/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # Analytics test application. target 'testapp' do - pod 'Firebase/Analytics' + pod 'Firebase/Analytics', '10.25.0' end diff --git a/analytics/testapp/build.gradle b/analytics/testapp/build.gradle index ddcfe2c3..42939198 100644 --- a/analytics/testapp/build.gradle +++ b/analytics/testapp/build.gradle @@ -2,117 +2,75 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.android.analytics.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.android.analytics.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/analytics.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile 'com.google.firebase:firebase-analytics:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + analytics } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/analytics/testapp/gradle.properties b/analytics/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/analytics/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/analytics/testapp/gradle/wrapper/gradle-wrapper.properties b/analytics/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/analytics/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/analytics/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/analytics/testapp/jni/Android.mk b/analytics/testapp/jni/Android.mk deleted file mode 100644 index c48cac71..00000000 --- a/analytics/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_analytics -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libanalytics.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_analytics \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/analytics/testapp/jni/Application.mk b/analytics/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/analytics/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/analytics/testapp/proguard.pro b/analytics/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/analytics/testapp/proguard.pro +++ b/analytics/testapp/proguard.pro @@ -1 +1,2 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/analytics/testapp/readme.md b/analytics/testapp/readme.md index 0b328d9a..374488a6 100644 --- a/analytics/testapp/readme.md +++ b/analytics/testapp/readme.md @@ -17,16 +17,16 @@ Building and Running the testapp - Link your iOS app to the Firebase libraries. - Get CocoaPods version 1 or later by running, ``` - $ sudo gem install CocoaPods --pre + sudo gem install cocoapods --pre ``` - From the testapp directory, install the CocoaPods listed in the Podfile by running, ``` - $ pod install + pod install ``` - Open the generated Xcode workspace (which now has the CocoaPods), ``` - $ open testapp.xcworkspace + open testapp.xcworkspace ``` - For further details please refer to the [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). @@ -39,8 +39,8 @@ Building and Running the testapp console to the testapp root directory. This file identifies your iOS app to the Firebase backend. - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework - frameworks/ios/universal/firebase\_analytics.framework @@ -62,7 +62,7 @@ Building and Running the testapp "View --> Debug Area --> Activate Console" from the menu. - After 5 hours, data should be visible in the Firebase Console under the "Analytics" tab accessible from - https://firebase.google.com/console/. + [https://firebase.google.com/console/](https://firebase.google.com/console/). ### Android - Register your Android app with Firebase. @@ -90,18 +90,19 @@ Building and Running the testapp - For further details please refer to the [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Configure the location of the Firebase C++ SDK by setting the firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For example, in the project directory: ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties ``` - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. - Open *build.gradle* in Android Studio. - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. @@ -112,12 +113,56 @@ Building and Running the testapp the command line. - After 5 hours, data should be visible in the Firebase Console under the "Analytics" tab accessible from - https://firebase.google.com/console/. + [https://firebase.google.com/console/](https://firebase.google.com/console/). + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. Note that Analytics uses a stubbed implementation on desktop, + so functionality is not expected. Support ------- -https://firebase.google.com/support/ +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -138,4 +183,3 @@ 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. - diff --git a/analytics/testapp/settings.gradle b/analytics/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/analytics/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/analytics/testapp/src/common_main.cc b/analytics/testapp/src/common_main.cc index 9b063622..e063e416 100644 --- a/analytics/testapp/src/common_main.cc +++ b/analytics/testapp/src/common_main.cc @@ -40,10 +40,22 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Enabling data collection."); analytics::SetAnalyticsCollectionEnabled(true); - // App needs to be open at least 1s before logging a valid session. - analytics::SetMinimumSessionDuration(1000); - // App session times out after 5s. - analytics::SetSessionTimeoutDuration(5000); + // App session times out after 30 minutes. + // If the app is placed in the background and returns to the foreground after + // the timeout is expired analytics will log a new session. + analytics::SetSessionTimeoutDuration(1000 * 60 * 30); + + LogMessage("Get App Instance ID..."); + auto future_result = analytics::GetAnalyticsInstanceId(); + while (future_result.status() == firebase::kFutureStatusPending) { + if (ProcessEvents(1000)) break; + } + if (future_result.status() == firebase::kFutureStatusComplete) { + LogMessage("Analytics Instance ID %s", future_result.result()->c_str()); + } else { + LogMessage("ERROR: Failed to fetch Analytics Instance ID %s (%d)", + future_result.error_message(), future_result.error()); + } LogMessage("Set user properties."); // Set the user's sign up method. @@ -51,9 +63,9 @@ extern "C" int common_main(int argc, const char* argv[]) { // Set the user ID. analytics::SetUserId("uber_user_510"); - LogMessage("Set current screen."); - // Set the user's current screen. - analytics::SetCurrentScreen("Firebase Analytics C++ testapp", "testapp"); + LogMessage("Log current screen."); + // Log the user's current screen. + analytics::LogEvent(analytics::kEventScreenView, "Firebase Analytics C++ testapp", "testapp" ); // Log an event with no parameters. LogMessage("Log login event."); diff --git a/analytics/testapp/src/desktop/desktop_main.cc b/analytics/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/analytics/testapp/src/desktop/desktop_main.cc +++ b/analytics/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/analytics/testapp/testapp.xcodeproj/project.pbxproj b/analytics/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/analytics/testapp/testapp.xcodeproj/project.pbxproj +++ b/analytics/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/analytics/testapp/testapp/Info.plist b/analytics/testapp/testapp/Info.plist index d938a81f..4a067783 100644 --- a/analytics/testapp/testapp/Info.plist +++ b/analytics/testapp/testapp/Info.plist @@ -27,7 +27,7 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID diff --git a/auth/testapp/AndroidManifest.xml b/auth/testapp/AndroidManifest.xml index a4705fe4..540dec54 100644 --- a/auth/testapp/AndroidManifest.xml +++ b/auth/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + ) + 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_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/auth/testapp/Podfile b/auth/testapp/Podfile index ff799f87..825abee2 100644 --- a/auth/testapp/Podfile +++ b/auth/testapp/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # Auth test application. target 'testapp' do - pod 'Firebase/Auth' + pod 'Firebase/Auth', '10.25.0' end diff --git a/auth/testapp/build.gradle b/auth/testapp/build.gradle index b35f5d66..eb739709 100644 --- a/auth/testapp/build.gradle +++ b/auth/testapp/build.gradle @@ -2,117 +2,76 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } +android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 } -} -android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.android.auth.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.android.auth.testapp' + minSdkVersion 23 + targetSdkVersion 34 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/auth.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile 'com.google.firebase:firebase-auth:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/auth/testapp/gradle.properties b/auth/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/auth/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/auth/testapp/gradle/wrapper/gradle-wrapper.properties b/auth/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/auth/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/auth/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/auth/testapp/jni/Android.mk b/auth/testapp/jni/Android.mk deleted file mode 100644 index 3f8e180f..00000000 --- a/auth/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_auth -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libauth.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_auth \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/auth/testapp/jni/Application.mk b/auth/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/auth/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/auth/testapp/proguard.pro b/auth/testapp/proguard.pro index c7e9278d..5edad15e 100644 --- a/auth/testapp/proguard.pro +++ b/auth/testapp/proguard.pro @@ -1 +1,3 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } +-keep,includedescriptorclasses public class com.google.firebase.example.TextEntryField { *; } diff --git a/auth/testapp/readme.md b/auth/testapp/readme.md index bb9448f2..04dd6fd0 100644 --- a/auth/testapp/readme.md +++ b/auth/testapp/readme.md @@ -33,23 +33,23 @@ Building and Running the testapp - Link your iOS app to the Firebase libraries. - Get CocoaPods version 1 or later by running, ``` - $ sudo gem install CocoaPods --pre + sudo gem install cocoapods --pre ``` - From the testapp directory, install the CocoaPods listed in the Podfile by running, ``` - $ pod install + pod install ``` - Open the generated Xcode workspace (which now has the CocoaPods), ``` - $ open testapp.xcworkspace + open testapp.xcworkspace ``` - For further details please refer to the [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). - Register your iOS app with Firebase. - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach your iOS app to it. - - You can use "com.google.ios.auth.testapp" as the iOS Bundle ID + - You can use "com.google.FirebaseCppAuthTestApp.dev" as the iOS Bundle ID while you're testing. You can omit App Store ID while testing. - Add the GoogleService-Info.plist that you downloaded from Firebase console to the testapp root directory. This file identifies your iOS app @@ -58,8 +58,8 @@ Building and Running the testapp enable "Anonymous". This will allow the testapp to use email accounts and anonymous sign-in. - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework - frameworks/ios/universal/firebase_auth.framework @@ -75,7 +75,16 @@ Building and Running the testapp Select the "Build Settings" tab, and click "All" to see all the build settings. Scroll down to "Search Paths", and add your path to "Framework Search Paths". + - Configure the XCode project for push messaging. + - Select the `Capabilities` tab in the XCode project. + - Switch `Push Notifications` to `On`. - In XCode, build & run the sample on an iOS device or simulator. + - Phone authentication needs to launch a webview and return the results to the + application. To do this it requires you configure a URL type to handle the + callback. In your project's Info tab, under the URL Types section, find + the URL Schemes box containing YOUR\_REVERSED\_CLIENT\_ID. Replace this + with the value of the REVERSED\_CLIENT\_ID string in + GoogleService-Info.plist. - The testapp has no user interface. The output of the app can be viewed via the console. In Xcode, select "View --> Debug Area --> Activate Console" from the menu. @@ -110,18 +119,19 @@ Building and Running the testapp - For further details please refer to the [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Configure the location of the Firebase C++ SDK by setting the firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For example, in the project directory: ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties ``` - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. - Open *build.gradle* in Android Studio. - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. @@ -131,38 +141,51 @@ Building and Running the testapp in the logcat output of Android studio or by running "adb logcat *:W android_main firebase" from the command line. -Known issues ------------- - - - Auth::SignInAnonymously() and Auth::CreateUserWithEmailAndPassword() are - temporarily broken. For the moment, they will always return - kAuthError_GeneralBackendError. - - User::UpdateUserProfile() currently fails to set the photo URL on both - Android and iOS, and also fails to set the display name on Android. - - The iOS testapp generates benign asserts of the form: - ``` - assertion failed: 15D21 13C75: assertiond + 12188 - [8CF1968D-3466-38B7-B225-3F6F5B64C552]: 0x1 - ``` - - Several API function are pending competion and will return status - `firebase::kAuthError_Unimplemented` or empty data. - - `Auth::ConfirmPasswordReset()` - - `Auth::CheckActionCode()` - - `Auth::ApplyActionCode()` - - `User::Reauthenticate()` - - `User::SendEmailVerification()` - - `User::Delete()` - - `User::ProviderData()` - - Several API functions return different error codes on iOS and Android. - The disparities will be eliminated in a subsequent release. - - When given invalid parameters, several API functions return - kAuthError_GeneralBackendError instead of kAuthError_InvalidEmail or - kAuthError_EmailNotFound. +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the console. Support ------- -https://firebase.google.com/support/ +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -183,4 +206,3 @@ 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. - diff --git a/auth/testapp/settings.gradle b/auth/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/auth/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/auth/testapp/src/android/android_main.cc b/auth/testapp/src/android/android_main.cc index 73cb30e7..02ef221d 100644 --- a/auth/testapp/src/android/android_main.cc +++ b/auth/testapp/src/android/android_main.cc @@ -148,6 +148,85 @@ class LoggingUtilsData { LoggingUtilsData* g_logging_utils_data; +// Vars that we need available for reading text from the user. +class TextEntryFieldData { + public: + TextEntryFieldData() + : text_entry_field_class_(nullptr), text_entry_field_read_text_(0) {} + + ~TextEntryFieldData() { + JNIEnv* env = GetJniEnv(); + assert(env); + if (text_entry_field_class_) { + env->DeleteGlobalRef(text_entry_field_class_); + } + } + + void Init() { + JNIEnv* env = GetJniEnv(); + assert(env); + + jclass text_entry_field_class = FindClass( + env, GetActivity(), "com/google/firebase/example/TextEntryField"); + assert(text_entry_field_class != 0); + + // Need to store as global references so it don't get moved during garbage + // collection. + text_entry_field_class_ = + static_cast(env->NewGlobalRef(text_entry_field_class)); + env->DeleteLocalRef(text_entry_field_class); + + static const JNINativeMethod kNativeMethods[] = { + {"nativeSleep", "(I)Z", reinterpret_cast(ProcessEvents)}}; + env->RegisterNatives(text_entry_field_class_, kNativeMethods, + sizeof(kNativeMethods) / sizeof(kNativeMethods[0])); + text_entry_field_read_text_ = env->GetStaticMethodID( + text_entry_field_class_, "readText", + "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;" + "Ljava/lang/String;)Ljava/lang/String;"); + } + + // Call TextEntryField.readText(), which shows a text entry dialog and spins + // until the user enters some text (or cancels). If the user cancels, returns + // an empty string. + std::string ReadText(const char* title, const char* message, + const char* placeholder) { + if (text_entry_field_class_ == 0) return ""; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + jstring title_string = env->NewStringUTF(title); + jstring message_string = env->NewStringUTF(message); + jstring placeholder_string = env->NewStringUTF(placeholder); + jobject result_string = env->CallStaticObjectMethod( + text_entry_field_class_, text_entry_field_read_text_, GetActivity(), + title_string, message_string, placeholder_string); + env->DeleteLocalRef(title_string); + env->DeleteLocalRef(message_string); + env->DeleteLocalRef(placeholder_string); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + if (result_string == nullptr) { + // Check if readText() returned null, which will be the case if an + // exception occurred or if TextEntryField returned null for some reason. + return ""; + } + const char* result_buffer = + env->GetStringUTFChars(static_cast(result_string), 0); + std::string result(result_buffer); + env->ReleaseStringUTFChars(static_cast(result_string), + result_buffer); + return result; + } + + private: + jclass text_entry_field_class_; + jmethodID text_entry_field_read_text_; +}; + +TextEntryFieldData* g_text_entry_field_data; + // Checks if a JNI exception has happened, and if so, logs it to the console. void CheckJNIException() { JNIEnv* env = GetJniEnv(); @@ -208,6 +287,15 @@ JNIEnv* GetJniEnv() { return result == JNI_OK ? env : nullptr; } +// Use a Java class, TextEntryField, to prompt the user to enter some text. +// This function blocks until text was entered or the dialog was canceled. +// If the user cancels, returns an empty string. +std::string ReadTextInput(const char* title, const char* message, + const char* placeholder) { + assert(g_text_entry_field_data); + return g_text_entry_field_data->ReadText(title, message, placeholder); +} + // Execute common_main(), flush pending events and finish the activity. extern "C" void android_main(struct android_app* state) { // native_app_glue spawns a new thread, calling android_main() when the @@ -235,6 +323,10 @@ extern "C" void android_main(struct android_app* state) { g_logging_utils_data = new LoggingUtilsData(); g_logging_utils_data->Init(); + // Create the text entry dialog. + g_text_entry_field_data = new TextEntryFieldData(); + g_text_entry_field_data->Init(); + // Execute cross platform entry point. static const char* argv[] = {FIREBASE_TESTAPP_NAME}; int return_value = common_main(1, argv); diff --git a/auth/testapp/src/android/java/com/google/firebase/example/TextEntryField.java b/auth/testapp/src/android/java/com/google/firebase/example/TextEntryField.java new file mode 100644 index 00000000..34c59fa5 --- /dev/null +++ b/auth/testapp/src/android/java/com/google/firebase/example/TextEntryField.java @@ -0,0 +1,106 @@ +// Copyright 2016 Google Inc. 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.EditText; + +/** + * A utility class, with a method to prompt the user to enter a line of text, and a native method to + * sleep for a given number of milliseconds. + */ +public class TextEntryField { + private static Object lock = new Object(); + private static String resultText = null; + + /** + * Prompt the user with a text field, blocking until the user fills it out, then returns the text + * they entered. If the user cancels, returns an empty string. + */ + public static String readText( + final Activity activity, final String title, final String message, final String placeholder) { + resultText = null; + // Show the alert dialog on the main thread. + activity.runOnUiThread( + new Runnable() { + @Override + public void run() { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity); + alertBuilder.setTitle(title); + alertBuilder.setMessage(message); + + // Set up and add the text field. + final EditText textField = new EditText(activity); + textField.setHint(placeholder); + alertBuilder.setView(textField); + + alertBuilder.setPositiveButton( + "OK", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + synchronized (lock) { + resultText = textField.getText().toString(); + } + } + }); + + alertBuilder.setNegativeButton( + "Cancel", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + synchronized (lock) { + resultText = ""; + } + } + }); + + alertBuilder.setOnCancelListener( + new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + synchronized (lock) { + resultText = ""; + } + } + }); + alertBuilder.show(); + } + }); + + // In our original thread, wait for the dialog to finish, then return its result. + while (true) { + // Pause a second, waiting for the user to enter text. + if (nativeSleep(1000)) { + // If this returns true, an exit was requested. + return ""; + } + synchronized (lock) { + if (resultText != null) { + // resultText will be set to non-null when a dialog button is clicked, or the dialog + // is canceled. + String result = resultText; + resultText = null; // Consume the result. + return result; + } + } + } + } + + private static native boolean nativeSleep(int milliseconds); +} diff --git a/auth/testapp/src/common_main.cc b/auth/testapp/src/common_main.cc index b47f7bb1..f0d12c7b 100644 --- a/auth/testapp/src/common_main.cc +++ b/auth/testapp/src/common_main.cc @@ -18,6 +18,7 @@ #include "firebase/app.h" #include "firebase/auth.h" +#include "firebase/auth/credential.h" #include "firebase/util.h" // Thin OS abstraction layer. @@ -27,18 +28,33 @@ using ::firebase::App; using ::firebase::AppOptions; using ::firebase::Future; using ::firebase::FutureBase; +using ::firebase::Variant; +using ::firebase::auth::AdditionalUserInfo; using ::firebase::auth::Auth; -using ::firebase::auth::Credential; using ::firebase::auth::AuthError; -using ::firebase::auth::kAuthErrorNone; -using ::firebase::auth::kAuthErrorFailure; +using ::firebase::auth::AuthResult; +using ::firebase::auth::Credential; using ::firebase::auth::EmailAuthProvider; using ::firebase::auth::FacebookAuthProvider; using ::firebase::auth::GitHubAuthProvider; using ::firebase::auth::GoogleAuthProvider; +using ::firebase::auth::kAuthErrorFailure; +using ::firebase::auth::kAuthErrorInvalidCredential; +using ::firebase::auth::kAuthErrorInvalidProviderId; +using ::firebase::auth::kAuthErrorNone; +using ::firebase::auth::OAuthProvider; +using ::firebase::auth::PhoneAuthOptions; +using ::firebase::auth::PhoneAuthProvider; +using ::firebase::auth::PhoneAuthCredential; +using ::firebase::auth::PlayGamesAuthProvider; using ::firebase::auth::TwitterAuthProvider; using ::firebase::auth::User; using ::firebase::auth::UserInfoInterface; +using ::firebase::auth::UserMetadata; + +#if TARGET_OS_IPHONE +using ::firebase::auth::GameCenterAuthProvider; +#endif // Set this to true, and set the email/password, to test a custom email address. static const bool kTestCustomEmail = false; @@ -46,12 +62,20 @@ static const char kCustomEmail[] = "custom.email@example.com"; static const char kCustomPassword[] = "CustomPasswordGoesHere"; // Constants used during tests. +static const char kTestNonceBad[] = "testBadNonce"; static const char kTestPassword[] = "testEmailPassword123"; static const char kTestEmailBad[] = "bad.test.email@example.com"; static const char kTestPasswordBad[] = "badTestPassword"; static const char kTestIdTokenBad[] = "bad id token for testing"; static const char kTestAccessTokenBad[] = "bad access token for testing"; static const char kTestPasswordUpdated[] = "testpasswordupdated"; +static const char kTestIdProviderIdBad[] = "bad provider id for testing"; +static const char kTestServerAuthCodeBad[] = "bad server auth code"; + +static const int kWaitIntervalMs = 300; +static const int kPhoneAuthCodeSendWaitMs = 600000; +static const int kPhoneAuthCompletionWaitMs = 8000; +static const int kPhoneAuthTimeoutMs = 0; static const char kFirebaseProviderId[] = #if defined(__ANDROID__) @@ -63,7 +87,7 @@ static const char kFirebaseProviderId[] = // Don't return until `future` is complete. // Print a message for whether the result mathes our expectations. // Returns true if the application should exit. -static bool WaitForFuture(FutureBase future, const char* fn, +static bool WaitForFuture(const FutureBase& future, const char* fn, AuthError expected_error, bool log_error = true) { // Note if the future has not be started properly. if (future.status() == ::firebase::kFutureStatusInvalid) { @@ -96,27 +120,51 @@ static bool WaitForFuture(FutureBase future, const char* fn, return false; } -static bool WaitForSignInFuture(Future sign_in_future, const char* fn, +static bool WaitForSignInFuture(Future sign_in_future, const char* fn, AuthError expected_error, Auth* auth) { if (WaitForFuture(sign_in_future, fn, expected_error)) return true; - const User* const* sign_in_user_ptr = sign_in_future.result(); - const User* sign_in_user = - sign_in_user_ptr == nullptr ? nullptr : *sign_in_user_ptr; - const User* auth_user = auth->current_user(); + const User sign_in_user = sign_in_future.result() ? *sign_in_future.result() : + User(); + const User auth_user = auth->current_user(); + + if (expected_error == ::firebase::auth::kAuthErrorNone && + sign_in_user != auth_user) { + LogMessage("ERROR: future's user (%s) and current_user (%s) don't match", + sign_in_user.uid().c_str(), + auth_user.uid().c_str()); + } + + return false; +} + +static bool WaitForSignInFuture(const Future& sign_in_future, + const char* fn, AuthError expected_error, + Auth* auth) { + if (WaitForFuture(sign_in_future, fn, expected_error)) return true; + + const AuthResult* auth_result = sign_in_future.result(); + const User sign_in_user = auth_result ? auth_result->user : User(); + const User auth_user = auth->current_user(); - if (sign_in_user != auth_user) { - LogMessage("ERROR: future's user (%x) and current_user (%x) don't match", - static_cast(reinterpret_cast(sign_in_user)), - static_cast(reinterpret_cast(auth_user))); + if (expected_error == ::firebase::auth::kAuthErrorNone && + sign_in_user != auth_user) { + LogMessage("ERROR: future's user (%s) and current_user (%s) don't match", + sign_in_user.uid().c_str(), + auth_user.uid().c_str()); } - const bool should_be_null = expected_error != kAuthErrorNone; - const bool is_null = sign_in_user == nullptr; - if (should_be_null != is_null) { - LogMessage("ERROR: user pointer (%x) is incorrect", - static_cast(reinterpret_cast(auth_user))); + return false; +} + +// Wait for the current user to sign out. Typically you should use the +// state listener to determine whether the user has signed out. +static bool WaitForSignOut(firebase::auth::Auth* auth) { + while (!auth->current_user().is_valid()) { + if (ProcessEvents(100)) return true; } + // Wait - hopefully - long enough for listeners to be signalled. + ProcessEvents(1000); return false; } @@ -154,11 +202,82 @@ static void ExpectStringsEqual(const char* test, const char* expected, } } +static void LogVariantMap(const std::map& variant_map, + int indent); + +// Log a vector of variants. +static void LogVariantVector(const std::vector& variants, int indent) { + std::string indent_string(indent * 2, ' '); + LogMessage("%s[", indent_string.c_str()); + for (auto it = variants.begin(); it != variants.end(); ++it) { + const Variant& item = *it; + if (item.is_fundamental_type()) { + const Variant& string_value = item.AsString(); + LogMessage("%s %s,", indent_string.c_str(), string_value.string_value()); + } else if (item.is_vector()) { + LogVariantVector(item.vector(), indent + 2); + } else if (item.is_map()) { + LogVariantMap(item.map(), indent + 2); + } else { + LogMessage("%s ERROR: unknown type %d", indent_string.c_str(), + static_cast(item.type())); + } + } + LogMessage("%s]", indent_string.c_str()); +} + +// Log a map of variants. +static void LogVariantMap(const std::map& variant_map, + int indent) { + std::string indent_string(indent * 2, ' '); + for (auto it = variant_map.begin(); it != variant_map.end(); ++it) { + const Variant& key_string = it->first.AsString(); + const Variant& value = it->second; + if (value.is_fundamental_type()) { + const Variant& string_value = value.AsString(); + LogMessage("%s%s: %s,", indent_string.c_str(), key_string.string_value(), + string_value.string_value()); + } else { + LogMessage("%s%s:", indent_string.c_str(), key_string.string_value()); + if (value.is_vector()) { + LogVariantVector(value.vector(), indent + 1); + } else if (value.is_map()) { + LogVariantMap(value.map(), indent + 1); + } else { + LogMessage("%s ERROR: unknown type %d", indent_string.c_str(), + static_cast(value.type())); + } + } + } +} + +// Display the sign-in result. +static void LogAuthResult(const AuthResult result) { + if (!result.user.is_valid()) { + LogMessage("ERROR: User not signed in"); + return; + } + LogMessage("* User ID %s", result.user.uid().c_str()); + const AdditionalUserInfo& info = result.additional_user_info; + LogMessage("* Provider ID %s", info.provider_id.c_str()); + LogMessage("* User Name %s", info.user_name.c_str()); + LogVariantMap(info.profile, 0); + UserMetadata metadata = result.user.metadata(); + LogMessage("* Sign in timestamp %d", + static_cast(metadata.last_sign_in_timestamp)); + LogMessage("* Creation timestamp %d", + static_cast(metadata.creation_timestamp)); +} + class AuthStateChangeCounter : public firebase::auth::AuthStateListener { public: AuthStateChangeCounter() : num_state_changes_(0) {} - virtual void OnAuthStateChanged(Auth* auth) { num_state_changes_++; } + virtual void OnAuthStateChanged(Auth* auth) { // NOLINT + num_state_changes_++; + LogMessage("OnAuthStateChanged User %s (state changes %d)", + auth->current_user().uid().c_str(), num_state_changes_); + } void CompleteTest(const char* test_name, int expected_state_changes) { CompleteTest(test_name, expected_state_changes, expected_state_changes); @@ -178,6 +297,34 @@ class AuthStateChangeCounter : public firebase::auth::AuthStateListener { int num_state_changes_; }; +class IdTokenChangeCounter : public firebase::auth::IdTokenListener { + public: + IdTokenChangeCounter() : num_token_changes_(0) {} + + virtual void OnIdTokenChanged(Auth* auth) { // NOLINT + num_token_changes_++; + LogMessage("OnIdTokenChanged User %s (token changes %d)", + auth->current_user().uid().c_str(), num_token_changes_); + } + + void CompleteTest(const char* test_name, int token_changes) { + CompleteTest(test_name, token_changes, token_changes); + } + + void CompleteTest(const char* test_name, int min_token_changes, + int max_token_changes) { + const bool success = min_token_changes <= num_token_changes_ && + num_token_changes_ <= max_token_changes; + LogMessage("%sIdTokenListener called %d time%s on %s.", + success ? "" : "ERROR: ", num_token_changes_, + num_token_changes_ == 1 ? "" : "s", test_name); + num_token_changes_ = 0; + } + + private: + int num_token_changes_; +}; + // Utility class for holding a user's login credentials. class UserLogin { public: @@ -185,7 +332,6 @@ class UserLogin { : auth_(auth), email_(email), password_(password), - user_(nullptr), log_errors_(true) {} explicit UserLogin(Auth* auth) : auth_(auth) { @@ -194,48 +340,48 @@ class UserLogin { } ~UserLogin() { - if (user_ != nullptr) { + if (user_.is_valid()) { log_errors_ = false; Delete(); } } void Register() { - Future register_test_account = + Future register_test_account = auth_->CreateUserWithEmailAndPassword(email(), password()); WaitForSignInFuture(register_test_account, "CreateUserWithEmailAndPassword() to create temp user", kAuthErrorNone, auth_); - user_ = register_test_account.result() ? *register_test_account.result() - : nullptr; + user_ = register_test_account.result() ? register_test_account.result()->user + : User(); } void Login() { Credential email_cred = EmailAuthProvider::GetCredential(email(), password()); - Future sign_in_cred = auth_->SignInWithCredential(email_cred); + Future sign_in_cred = auth_->SignInWithCredential(email_cred); WaitForSignInFuture(sign_in_cred, "Auth::SignInWithCredential() for UserLogin", kAuthErrorNone, auth_); } void Delete() { - if (user_ != nullptr) { - Future delete_future = user_->Delete(); + if (user_.is_valid()) { + Future delete_future = user_.Delete(); if (delete_future.status() == ::firebase::kFutureStatusInvalid) { Login(); - delete_future = user_->Delete(); + delete_future = user_.Delete(); } WaitForFuture(delete_future, "User::Delete()", kAuthErrorNone, log_errors_); } - user_ = nullptr; + user_ = User(); } const char* email() const { return email_.c_str(); } const char* password() const { return password_.c_str(); } - User* user() const { return user_; } + User user() const { return user_; } void set_email(const char* email) { email_ = email; } void set_password(const char* password) { password_ = password; } @@ -243,10 +389,70 @@ class UserLogin { Auth* auth_; std::string email_; std::string password_; - User* user_; + User user_; bool log_errors_; }; +class PhoneListener : public PhoneAuthProvider::Listener { + public: + PhoneListener() + : num_calls_on_verification_complete_(0), + num_calls_on_verification_failed_(0), + num_calls_on_code_sent_(0), + num_calls_on_code_auto_retrieval_time_out_(0) {} + + void OnVerificationCompleted(PhoneAuthCredential /*credential*/) override { + LogMessage("PhoneListener: successful automatic verification."); + num_calls_on_verification_complete_++; + } + + void OnVerificationFailed(const std::string& error) override { + LogMessage("ERROR: PhoneListener verification failed with error, %s", + error.c_str()); + num_calls_on_verification_failed_++; + } + + void OnCodeSent(const std::string& verification_id, + const PhoneAuthProvider::ForceResendingToken& + force_resending_token) override { + LogMessage("PhoneListener: code sent. verification_id=%s", + verification_id.c_str()); + verification_id_ = verification_id; + force_resending_token_ = force_resending_token; + num_calls_on_code_sent_++; + } + + void OnCodeAutoRetrievalTimeOut(const std::string& verification_id) override { + LogMessage("PhoneListener: auto retrieval timeout. verification_id=%s", + verification_id.c_str()); + verification_id_ = verification_id; + num_calls_on_code_auto_retrieval_time_out_++; + } + + const std::string& verification_id() const { return verification_id_; } + const PhoneAuthProvider::ForceResendingToken& force_resending_token() const { + return force_resending_token_; + } + int num_calls_on_verification_complete() const { + return num_calls_on_verification_complete_; + } + int num_calls_on_verification_failed() const { + return num_calls_on_verification_failed_; + } + int num_calls_on_code_sent() const { return num_calls_on_code_sent_; } + int num_calls_on_code_auto_retrieval_time_out() const { + return num_calls_on_code_auto_retrieval_time_out_; + } + + private: + std::string verification_id_; + PhoneAuthProvider::ForceResendingToken force_resending_token_; + int num_calls_on_verification_complete_; + int num_calls_on_verification_failed_; + int num_calls_on_code_sent_; + int num_calls_on_code_auto_retrieval_time_out_; +}; + // Execute all methods of the C++ Auth API. extern "C" int common_main(int argc, const char* argv[]) { App* app; @@ -287,11 +493,13 @@ extern "C" int common_main(int argc, const char* argv[]) { // It's possible for current_user() to be non-null if the previous run // left us in a signed-in state. - if (auth->current_user() == nullptr) { + if (!auth->current_user().is_valid()) { LogMessage("No user signed in at creation time."); } else { - LogMessage("Current user %s already signed in, so signing them out.", - auth->current_user()->display_name().c_str()); + LogMessage( + "Current user uid(%s) name(%s) already signed in, so signing them out.", + auth->current_user().uid().c_str(), + auth->current_user().display_name().c_str()); auth->SignOut(); } @@ -315,7 +523,7 @@ extern "C" int common_main(int argc, const char* argv[]) { if (kTestCustomEmail) { // Test Auth::SignInWithEmailAndPassword(). // Sign in with email and password that have already been registered. - Future sign_in_future = + Future sign_in_future = auth->SignInWithEmailAndPassword(kCustomEmail, kCustomPassword); WaitForSignInFuture(sign_in_future, "Auth::SignInWithEmailAndPassword() existing " @@ -324,11 +532,11 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test SignOut() after signed in with email and password. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of NULL after " + "ERROR: current_user() returning %s instead of nullptr after " "SignOut()", - auth->current_user()); + auth->current_user().uid().c_str()); } } } @@ -337,50 +545,146 @@ extern "C" int common_main(int argc, const char* argv[]) { // --- StateChange tests ----------------------------------------------------- { AuthStateChangeCounter counter; + IdTokenChangeCounter token_counter; // Test notification on registration. auth->AddAuthStateListener(&counter); - counter.CompleteTest("registration", 0); + auth->AddIdTokenListener(&token_counter); + // Expect notification immediately after registration. + counter.CompleteTest("registration", 1); + token_counter.CompleteTest("registration", 1); // Test notification on SignOut(), when already signed-out. auth->SignOut(); counter.CompleteTest("SignOut() when already signed-out", 0); + token_counter.CompleteTest("SignOut() when already signed-out", 0); // Test notification on SignIn(). - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously()", kAuthErrorNone, auth); + // Notified when the user is about to change and after the user has + // changed. counter.CompleteTest("SignInAnonymously()", 1, 4); + token_counter.CompleteTest("SignInAnonymously()", 1, 5); + + // Refresh the token. + if (auth->current_user().is_valid()) { + Future token_future = auth->current_user().GetToken(true); + WaitForFuture(token_future, "GetToken()", kAuthErrorNone); + counter.CompleteTest("GetToken()", 0); + token_counter.CompleteTest("GetToken()", 1); + } // Test notification on SignOut(), when signed-in. - // TODO(jsanmiya): Change the minimum expected callbacks to 1 once - // b/32179003 is fixed. + LogMessage("Current user %s", auth->current_user().uid().c_str()); // DEBUG auth->SignOut(); - counter.CompleteTest("SignOut()", 0, 4); + // Wait for the sign out to complete. + WaitForSignOut(auth); + counter.CompleteTest("SignOut()", 1); + token_counter.CompleteTest("SignOut()", 1); + LogMessage("Current user %s", auth->current_user().uid().c_str()); // DEBUG auth->RemoveAuthStateListener(&counter); + auth->RemoveIdTokenListener(&token_counter); + } + + // Phone verification isn't currently implemented on desktop +#if defined(__ANDROID__) || TARGET_OS_IPHONE + // --- PhoneListener tests --------------------------------------------------- + { + UserLogin user_login(auth); // Generate a random name/password + user_login.Register(); + + LogMessage("Verifying phone number"); + + const std::string phone_number = ReadTextInput( + "Phone Number", "Please enter your phone number", "+12345678900"); + PhoneListener listener; + PhoneAuthProvider& phone_provider = PhoneAuthProvider::GetInstance(auth); + PhoneAuthOptions options; + options.phone_number = phone_number; + options.timeout_milliseconds = kPhoneAuthTimeoutMs; + phone_provider.VerifyPhoneNumber(options, &listener); + + // Wait for OnCodeSent() callback. + int wait_ms = 0; + while (listener.num_calls_on_verification_complete() == 0 && + listener.num_calls_on_verification_failed() == 0 && + listener.num_calls_on_code_sent() == 0) { + if (wait_ms > kPhoneAuthCodeSendWaitMs) break; + ProcessEvents(kWaitIntervalMs); + wait_ms += kWaitIntervalMs; + LogMessage("."); + } + if (wait_ms > kPhoneAuthCodeSendWaitMs || + listener.num_calls_on_verification_failed()) { + LogMessage("ERROR: SMS with verification code not sent."); + } else { + LogMessage("SMS verification code sent."); + + const std::string verification_code = ReadTextInput( + "Verification Code", + "Please enter the verification code sent to you via SMS", "123456"); + + // Wait for one of the other callbacks. + while (listener.num_calls_on_verification_complete() == 0 && + listener.num_calls_on_verification_failed() == 0 && + listener.num_calls_on_code_auto_retrieval_time_out() == 0) { + if (wait_ms > kPhoneAuthCompletionWaitMs) break; + ProcessEvents(kWaitIntervalMs); + wait_ms += kWaitIntervalMs; + LogMessage("."); + } + if (listener.num_calls_on_code_auto_retrieval_time_out() > 0) { + const PhoneAuthCredential phone_credential = phone_provider.GetCredential( + listener.verification_id().c_str(), verification_code.c_str()); + + Futurephone_future = + auth->SignInWithCredential(phone_credential); + WaitForSignInFuture(phone_future, + "Auth::SignInWithCredential() phone credential", + kAuthErrorNone, auth); + if (phone_future.error() == kAuthErrorNone) { + User user = *phone_future.result(); + Future update_future = + user.UpdatePhoneNumberCredential(phone_credential); + WaitForSignInFuture( + update_future, + "user.UpdatePhoneNumberCredential(phone_credential)", + kAuthErrorNone, auth); + } + + } else { + LogMessage("ERROR: SMS auto-detect time out did not occur."); + } + } } +#endif // defined(__ANDROID__) || TARGET_OS_IPHONE // --- Auth tests ------------------------------------------------------------ { UserLogin user_login(auth); // Generate a random name/password user_login.Register(); - if (!user_login.user()) { + if (!user_login.user().is_valid()) { LogMessage("ERROR: Could not register new user."); } else { // Test Auth::SignInAnonymously(). { - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously()", kAuthErrorNone, auth); + ExpectTrue("SignInAnonymouslyLastResult matches returned Future", + sign_in_future == auth->SignInAnonymouslyLastResult()); + // Test SignOut() after signed in anonymously. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of NULL after " + "ERROR: current_user() returning valid user %s instead of invalid user after " "SignOut()", - auth->current_user()); + auth->current_user().uid().c_str()); } } } @@ -391,6 +695,10 @@ extern "C" int common_main(int argc, const char* argv[]) { auth->FetchProvidersForEmail(user_login.email()); WaitForFuture(providers_future, "Auth::FetchProvidersForEmail()", kAuthErrorNone); + ExpectTrue( + "FetchProvidersForEmailLastResult matches returned Future", + providers_future == auth->FetchProvidersForEmailLastResult()); + const Auth::FetchProvidersResult* pro = providers_future.result(); if (pro) { LogMessage(" email %s, num providers %d", user_login.email(), @@ -405,51 +713,123 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test Auth::SignInWithEmailAndPassword(). // Sign in with email and password that have already been registered. { - Future sign_in_future = auth->SignInWithEmailAndPassword( + Future sign_in_future = auth->SignInWithEmailAndPassword( user_login.email(), user_login.password()); WaitForSignInFuture( sign_in_future, "Auth::SignInWithEmailAndPassword() existing email and password", kAuthErrorNone, auth); + ExpectTrue( + "SignInWithEmailAndPasswordLastResult matches returned Future", + sign_in_future == auth->SignInWithEmailAndPasswordLastResult()); + // Test SignOut() after signed in with email and password. if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->current_user() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: current_user() returning %x instead of NULL after " - "SignOut()", - auth->current_user()); + "ERROR: current_user() returning valid user %s instead of invalid user after " + "SignOut()", auth->current_user().uid().c_str()); + } + } + } + + // Test User::UpdateUserProfile + { + Future sign_in_future = auth->SignInWithEmailAndPassword( + user_login.email(), user_login.password()); + WaitForSignInFuture( + sign_in_future, + "Auth::SignInWithEmailAndPassword() existing email and password", + kAuthErrorNone, auth); + if (sign_in_future.error() == kAuthErrorNone) { + User user = sign_in_future.result()->user; + const char* kDisplayName = "Hello World"; + const char* kPhotoUrl = "http://test.com/image.jpg"; + User::UserProfile user_profile; + user_profile.display_name = kDisplayName; + user_profile.photo_url = kPhotoUrl; + Future update_profile_future = + user.UpdateUserProfile(user_profile); + WaitForFuture(update_profile_future, "User::UpdateUserProfile", + kAuthErrorNone); + if (update_profile_future.error() == kAuthErrorNone) { + ExpectStringsEqual("User::display_name", kDisplayName, + user.display_name().c_str()); + ExpectStringsEqual("User::photo_url", kPhotoUrl, + user.photo_url().c_str()); + } + } + } + + // Sign in anonymously, link an email credential, reauthenticate with the + // credential, unlink the credential and finally sign out. + { + Future sign_in_anonymously_future = auth->SignInAnonymously(); + WaitForSignInFuture(sign_in_anonymously_future, + "Auth::SignInAnonymously", kAuthErrorNone, auth); + if (sign_in_anonymously_future.error() == kAuthErrorNone) { + User user = sign_in_anonymously_future.result()->user; + std::string email = CreateNewEmail(); + Credential credential = + EmailAuthProvider::GetCredential(email.c_str(), kTestPassword); + // Link with an email / password credential. + Future link_future = + user.LinkWithCredential(credential); + WaitForSignInFuture(link_future, + "User::LinkAndRetrieveDataWithCredential", + kAuthErrorNone, auth); + if (link_future.error() == kAuthErrorNone) { + LogAuthResult(*link_future.result()); + Future reauth_future = + user.ReauthenticateAndRetrieveData(credential); + WaitForSignInFuture(reauth_future, + "User::ReauthenticateAndRetrieveData", + kAuthErrorNone, auth); + if (reauth_future.error() == kAuthErrorNone) { + LogAuthResult(*reauth_future.result()); + } + // Unlink email / password from credential. + Future unlink_future = + user.Unlink(credential.provider().c_str()); + WaitForSignInFuture(unlink_future, "User::Unlink", kAuthErrorNone, + auth); } + auth->SignOut(); } } // Sign in user with bad email. Should fail. { - Future sign_in_future_bad_email = + Future sign_in_future_bad_email = auth->SignInWithEmailAndPassword(kTestEmailBad, kTestPassword); WaitForSignInFuture(sign_in_future_bad_email, "Auth::SignInWithEmailAndPassword() bad email", - kAuthErrorFailure, auth); + ::firebase::auth::kAuthErrorUserNotFound, auth); } // Sign in user with correct email but bad password. Should fail. { - Future sign_in_future_bad_password = + Future sign_in_future_bad_password = auth->SignInWithEmailAndPassword(user_login.email(), kTestPasswordBad); WaitForSignInFuture(sign_in_future_bad_password, "Auth::SignInWithEmailAndPassword() bad password", - kAuthErrorFailure, auth); + ::firebase::auth::kAuthErrorWrongPassword, auth); } // Try to create with existing email. Should fail. { - Future create_future_bad = auth->CreateUserWithEmailAndPassword( + Future create_future_bad = auth->CreateUserWithEmailAndPassword( user_login.email(), user_login.password()); WaitForSignInFuture( create_future_bad, "Auth::CreateUserWithEmailAndPassword() existing email", - kAuthErrorFailure, auth); + ::firebase::auth::kAuthErrorEmailAlreadyInUse, auth); + ExpectTrue( + "CreateUserWithEmailAndPasswordLastResult matches returned Future", + create_future_bad == + auth->CreateUserWithEmailAndPasswordLastResult()); } // Test Auth::SignInWithCredential() using email&password. @@ -457,54 +837,181 @@ extern "C" int common_main(int argc, const char* argv[]) { { Credential email_cred_ok = EmailAuthProvider::GetCredential( user_login.email(), user_login.password()); - Future sign_in_cred_ok = + Futuresign_in_cred_ok = auth->SignInWithCredential(email_cred_ok); WaitForSignInFuture(sign_in_cred_ok, "Auth::SignInWithCredential() existing email", kAuthErrorNone, auth); } + // Test Auth::SignInAndRetrieveDataWithCredential using email & password. + // Use existing email. Should succeed. + { + Credential email_cred = EmailAuthProvider::GetCredential( + user_login.email(), user_login.password()); + Future sign_in_future = + auth->SignInAndRetrieveDataWithCredential(email_cred); + WaitForSignInFuture(sign_in_future, + "Auth::SignInAndRetrieveDataWithCredential " + "existing email", + kAuthErrorNone, auth); + ExpectTrue( + "SignInAndRetrieveDataWithCredentialLastResult matches " + "returned Future", + sign_in_future == + auth->SignInAndRetrieveDataWithCredentialLastResult()); + if (sign_in_future.error() == kAuthErrorNone) { + const AuthResult* sign_in_result = sign_in_future.result(); + if (sign_in_result != nullptr && sign_in_result->user.is_valid()) { + LogMessage("SignInAndRetrieveDataWithCredential"); + LogAuthResult(*sign_in_result); + } else { + LogMessage( + "ERROR: SignInAndRetrieveDataWithCredential returned no " + "result"); + } + } + } + // Use bad Facebook credentials. Should fail. { Credential facebook_cred_bad = FacebookAuthProvider::GetCredential(kTestAccessTokenBad); - Future facebook_bad = + Futurefacebook_bad = auth->SignInWithCredential(facebook_cred_bad); WaitForSignInFuture( facebook_bad, "Auth::SignInWithCredential() bad Facebook credentials", - kAuthErrorFailure, auth); + kAuthErrorInvalidCredential, auth); } // Use bad GitHub credentials. Should fail. { Credential git_hub_cred_bad = GitHubAuthProvider::GetCredential(kTestAccessTokenBad); - Future git_hub_bad = + Futuregit_hub_bad = auth->SignInWithCredential(git_hub_cred_bad); WaitForSignInFuture( git_hub_bad, "Auth::SignInWithCredential() bad GitHub credentials", - kAuthErrorFailure, auth); + kAuthErrorInvalidCredential, auth); } // Use bad Google credentials. Should fail. { Credential google_cred_bad = GoogleAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future google_bad = auth->SignInWithCredential(google_cred_bad); + Futuregoogle_bad = auth->SignInWithCredential(google_cred_bad); WaitForSignInFuture( google_bad, "Auth::SignInWithCredential() bad Google credentials", - kAuthErrorFailure, auth); + kAuthErrorInvalidCredential, auth); + } + + // Use bad Google credentials, missing an optional parameter. Should fail. + { + Credential google_cred_bad = + GoogleAuthProvider::GetCredential(kTestIdTokenBad, nullptr); + Futuregoogle_bad = auth->SignInWithCredential(google_cred_bad); + WaitForSignInFuture( + google_bad, "Auth::SignInWithCredential() bad Google credentials", + kAuthErrorInvalidCredential, auth); + } + +#if defined(__ANDROID__) + // Use bad Play Games (Android-only) credentials. Should fail. + { + Credential play_games_cred_bad = + PlayGamesAuthProvider::GetCredential(kTestServerAuthCodeBad); + Futureplay_games_bad = + auth->SignInWithCredential(play_games_cred_bad); + WaitForSignInFuture( + play_games_bad, + "Auth:SignInWithCredential() bad Play Games credentials", + kAuthErrorInvalidCredential, auth); } +#endif // defined(__ANDROID__) +#if TARGET_OS_IPHONE + // Test Game Center status/login + { + // Check if the current user is authenticated to GameCenter + bool is_authenticated = GameCenterAuthProvider::IsPlayerAuthenticated(); + if (!is_authenticated) { + LogMessage("Not signed into Game Center, skipping test."); + } else { + LogMessage("Signed in, testing Game Center authentication."); + + // Get the Game Center credential from the device + Future game_center_credential_future = + GameCenterAuthProvider::GetCredential(); + WaitForFuture(game_center_credential_future, + "GameCenterAuthProvider::GetCredential()", + kAuthErrorNone); + + const AuthError credential_error = + static_cast(game_center_credential_future.error()); + + // Only attempt to sign in if we were able to get a credential. + if (credential_error == kAuthErrorNone) { + const Credential* gc_credential_ptr = + game_center_credential_future.result(); + + if (gc_credential_ptr == nullptr) { + LogMessage("Failed to retrieve Game Center credential."); + } else { + Futuregame_center_user = + auth->SignInWithCredential(*gc_credential_ptr); + WaitForFuture(game_center_user, + "Auth::SignInWithCredential() test Game Center " + "credential signin", + kAuthErrorNone); + } + } + } + } +#endif // TARGET_OS_IPHONE // Use bad Twitter credentials. Should fail. { Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( kTestIdTokenBad, kTestAccessTokenBad); - Future twitter_bad = + Futuretwitter_bad = auth->SignInWithCredential(twitter_cred_bad); WaitForSignInFuture( twitter_bad, "Auth::SignInWithCredential() bad Twitter credentials", + kAuthErrorInvalidCredential, auth); + } + + // Construct OAuthCredential with nonce & access token. + { + Credential nonce_credential_good = + OAuthProvider::GetCredential(kTestIdProviderIdBad, kTestIdTokenBad, + kTestNonceBad, kTestAccessTokenBad); + } + + // Construct OAuthCredential with nonce, null access token. + { + Credential nonce_credential_good = OAuthProvider::GetCredential( + kTestIdProviderIdBad, kTestIdTokenBad, kTestNonceBad, + /*access_token=*/nullptr); + } + + // Use bad OAuth credentials. Should fail. + { + Credential oauth_cred_bad = OAuthProvider::GetCredential( + kTestIdProviderIdBad, kTestIdTokenBad, kTestAccessTokenBad); + Futureoauth_bad = auth->SignInWithCredential(oauth_cred_bad); + WaitForSignInFuture( + oauth_bad, "Auth::SignInWithCredential() bad OAuth credentials", + kAuthErrorFailure, auth); + } + + // Use bad OAuth credentials with nonce. Should fail. + { + Credential oauth_cred_bad = + OAuthProvider::GetCredential(kTestIdProviderIdBad, kTestIdTokenBad, + kTestNonceBad, kTestAccessTokenBad); + Futureoauth_bad = auth->SignInWithCredential(oauth_cred_bad); + WaitForSignInFuture( + oauth_bad, "Auth::SignInWithCredential() bad OAuth credentials", kAuthErrorFailure, auth); } @@ -516,6 +1023,9 @@ extern "C" int common_main(int argc, const char* argv[]) { WaitForFuture(send_password_reset_ok, "Auth::SendPasswordResetEmail() existing email", kAuthErrorNone); + ExpectTrue( + "SendPasswordResetEmailLastResult matches returned Future", + send_password_reset_ok == auth->SendPasswordResetEmailLastResult()); } // Use bad email. Should fail. @@ -524,79 +1034,133 @@ extern "C" int common_main(int argc, const char* argv[]) { auth->SendPasswordResetEmail(kTestEmailBad); WaitForFuture(send_password_reset_bad, "Auth::SendPasswordResetEmail() bad email", - kAuthErrorFailure); + ::firebase::auth::kAuthErrorUserNotFound); } } } // --- User tests ------------------------------------------------------------ // Test anonymous user info strings. { - Future anon_sign_in_for_user = auth->SignInAnonymously(); + Future anon_sign_in_for_user = auth->SignInAnonymously(); WaitForSignInFuture(anon_sign_in_for_user, "Auth::SignInAnonymously() for User", kAuthErrorNone, auth); if (anon_sign_in_for_user.status() == ::firebase::kFutureStatusComplete) { - User* anonymous_user = anon_sign_in_for_user.result() - ? *anon_sign_in_for_user.result() - : nullptr; - if (anonymous_user != nullptr) { - LogMessage("Anonymous uid is %s", anonymous_user->uid().c_str()); + User anonymous_user = anon_sign_in_for_user.result() + ? anon_sign_in_for_user.result()->user + : User(); + if (anonymous_user.is_valid()) { + LogMessage("Anonymous uid is %s", anonymous_user.uid().c_str()); ExpectStringsEqual("Anonymous user email", "", - anonymous_user->email().c_str()); + anonymous_user.email().c_str()); ExpectStringsEqual("Anonymous user display_name", "", - anonymous_user->display_name().c_str()); + anonymous_user.display_name().c_str()); ExpectStringsEqual("Anonymous user photo_url", "", - anonymous_user->photo_url().c_str()); + anonymous_user.photo_url().c_str()); ExpectStringsEqual("Anonymous user provider_id", kFirebaseProviderId, - anonymous_user->provider_id().c_str()); - ExpectTrue("Anonymous email is_anonymous()", - anonymous_user->is_anonymous()); - ExpectFalse("Email email is_email_verified()", - anonymous_user->is_email_verified()); - - // Test User::LinkWithCredential(). + anonymous_user.provider_id().c_str()); + ExpectTrue("Anonymous user is_anonymous()", + anonymous_user.is_anonymous()); + ExpectFalse("Anonymous user is_email_verified()", + anonymous_user.is_email_verified()); + ExpectTrue("Anonymous user metadata().last_sign_in_timestamp != 0", + anonymous_user.metadata().last_sign_in_timestamp != 0); + ExpectTrue("Anonymous user metadata().creation_timestamp != 0", + anonymous_user.metadata().creation_timestamp != 0); + + // Test User::LinkWithCredential(), linking with email & password. const std::string newer_email = CreateNewEmail(); Credential user_cred = EmailAuthProvider::GetCredential( newer_email.c_str(), kTestPassword); - Future link_future = - anonymous_user->LinkWithCredential(user_cred); - WaitForSignInFuture(link_future, "User::LinkWithCredential()", - kAuthErrorNone, auth); + { + Future link_future = + anonymous_user.LinkWithCredential(user_cred); + WaitForSignInFuture(link_future, "User::LinkWithCredential()", + kAuthErrorNone, auth); + } + + // Test User::LinkWithCredential(), linking with same email & password. + { + Future link_future = + anonymous_user.LinkWithCredential(user_cred); + WaitForSignInFuture(link_future, "User::LinkWithCredential() again", + ::firebase::auth::kAuthErrorProviderAlreadyLinked, + auth); + } + + // Test User::LinkWithCredential(), linking with bad credential. + // Call should fail and Auth's current user should be maintained. + { + const User pre_link_user = auth->current_user(); + ExpectTrue("Test precondition requires active user", + pre_link_user.is_valid()); + + Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( + kTestIdTokenBad, kTestAccessTokenBad); + Future link_bad_future = + anonymous_user.LinkWithCredential(twitter_cred_bad); + WaitForFuture(link_bad_future, + "User::LinkWithCredential() with bad credential", + kAuthErrorInvalidCredential); + ExpectTrue("Linking maintains user", + auth->current_user() == pre_link_user); + } + + // Test Auth::SignInWithCredential(), signing in with bad credential. + // Call should fail, and Auth's current user should be maintained. + { + const User pre_signin_user = auth->current_user(); + ExpectTrue("Test precondition requires active user", + pre_signin_user.is_valid()); + Credential twitter_cred_bad = TwitterAuthProvider::GetCredential( + kTestIdTokenBad, kTestAccessTokenBad); + Futuresignin_bad_future = + auth->SignInWithCredential(twitter_cred_bad); + WaitForFuture(signin_bad_future, + "Auth::SignInWithCredential() with bad credential", + kAuthErrorInvalidCredential, auth); + ExpectTrue("Failed sign in maintains user", + auth->current_user() == pre_signin_user); + } UserLogin user_login(auth); user_login.Register(); - if (!user_login.user()) { + if (!user_login.user().is_valid()) { LogMessage("Error - Could not create new user."); } else { // Test email user info strings. - Future email_sign_in_for_user = + Future email_sign_in_for_user = auth->SignInWithEmailAndPassword(user_login.email(), user_login.password()); WaitForSignInFuture(email_sign_in_for_user, "Auth::SignInWithEmailAndPassword() for User", kAuthErrorNone, auth); - User* email_user = email_sign_in_for_user.result() - ? *email_sign_in_for_user.result() - : nullptr; - if (email_user != nullptr) { - LogMessage("Email uid is %s", email_user->uid().c_str()); + User email_user = email_sign_in_for_user.result() + ? email_sign_in_for_user.result()->user + : User(); + if (email_user.is_valid()) { + LogMessage("Email uid is %s", email_user.uid().c_str()); ExpectStringsEqual("Email user email", user_login.email(), - email_user->email().c_str()); + email_user.email().c_str()); ExpectStringsEqual("Email user display_name", "", - email_user->display_name().c_str()); + email_user.display_name().c_str()); ExpectStringsEqual("Email user photo_url", "", - email_user->photo_url().c_str()); + email_user.photo_url().c_str()); ExpectStringsEqual("Email user provider_id", kFirebaseProviderId, - email_user->provider_id().c_str()); - ExpectFalse("Email email is_anonymous()", - email_user->is_anonymous()); - ExpectFalse("Email email is_email_verified()", - email_user->is_email_verified()); + email_user.provider_id().c_str()); + ExpectFalse("Email user is_anonymous()", + email_user.is_anonymous()); + ExpectFalse("Email user is_email_verified()", + email_user.is_email_verified()); + ExpectTrue("Email user metadata().last_sign_in_timestamp != 0", + email_user.metadata().last_sign_in_timestamp != 0); + ExpectTrue("Email user metadata().creation_timestamp != 0", + email_user.metadata().creation_timestamp != 0); // Test User::GetToken(). // with force_refresh = false. - Future token_no_refresh = email_user->GetToken(false); + Future token_no_refresh = email_user.GetToken(false); WaitForFuture(token_no_refresh, "User::GetToken(false)", kAuthErrorNone); LogMessage("User::GetToken(false) = %s", @@ -606,7 +1170,7 @@ extern "C" int common_main(int argc, const char* argv[]) { // with force_refresh = true. Future token_force_refresh = - email_user->GetToken(true); + email_user.GetToken(true); WaitForFuture(token_force_refresh, "User::GetToken(true)", kAuthErrorNone); LogMessage("User::GetToken(true) = %s", @@ -615,62 +1179,59 @@ extern "C" int common_main(int argc, const char* argv[]) { : ""); // Test Reload(). - Future reload_future = email_user->Reload(); + Future reload_future = email_user.Reload(); WaitForFuture(reload_future, "User::Reload()", kAuthErrorNone); - // Test User::refresh_token(). - const std::string refresh_token = email_user->refresh_token(); - LogMessage("User::refresh_token() = %s", refresh_token.c_str()); - // Test User::Unlink(). - Future unlink_future = email_user->Unlink("firebase"); + Future unlink_future = email_user.Unlink("firebase"); WaitForSignInFuture(unlink_future, "User::Unlink()", - kAuthErrorFailure, auth); + ::firebase::auth::kAuthErrorNoSuchProvider, + auth); // Sign in again if user is now invalid. - if (auth->current_user() == nullptr) { - Future email_sign_in_again = + if (!auth->current_user().is_valid()) { + Future email_sign_in_again = auth->SignInWithEmailAndPassword(user_login.email(), user_login.password()); WaitForSignInFuture(email_sign_in_again, "Auth::SignInWithEmailAndPassword() again", kAuthErrorNone, auth); email_user = email_sign_in_again.result() - ? *email_sign_in_again.result() - : nullptr; + ? email_sign_in_again.result()->user + : User(); } } - if (email_user != nullptr) { + if (email_user.is_valid()) { // Test User::provider_data(). - const std::vector& provider_data = - email_user->provider_data(); + const std::vector provider_data = + email_user.provider_data(); LogMessage("User::provider_data() returned %d interface%s", provider_data.size(), provider_data.size() == 1 ? "" : "s"); for (size_t i = 0; i < provider_data.size(); ++i) { - const UserInfoInterface* user_info = provider_data[i]; + const UserInfoInterface user_info = provider_data[i]; LogMessage( " UID() = %s\n" " Email() = %s\n" " DisplayName() = %s\n" " PhotoUrl() = %s\n" " ProviderId() = %s", - user_info->uid().c_str(), user_info->email().c_str(), - user_info->display_name().c_str(), - user_info->photo_url().c_str(), - user_info->provider_id().c_str()); + user_info.uid().c_str(), user_info.email().c_str(), + user_info.display_name().c_str(), + user_info.photo_url().c_str(), + user_info.provider_id().c_str()); } // Test User::UpdateEmail(). const std::string newest_email = CreateNewEmail(); Future update_email_future = - email_user->UpdateEmail(newest_email.c_str()); + email_user.UpdateEmail(newest_email.c_str()); WaitForFuture(update_email_future, "User::UpdateEmail()", kAuthErrorNone); // Test User::UpdatePassword(). Future update_password_future = - email_user->UpdatePassword(kTestPasswordUpdated); + email_user.UpdatePassword(kTestPasswordUpdated); WaitForFuture(update_password_future, "User::UpdatePassword()", kAuthErrorNone); @@ -678,13 +1239,13 @@ extern "C" int common_main(int argc, const char* argv[]) { Credential email_cred_reauth = EmailAuthProvider::GetCredential( newest_email.c_str(), kTestPasswordUpdated); Future reauthenticate_future = - email_user->Reauthenticate(email_cred_reauth); + email_user.Reauthenticate(email_cred_reauth); WaitForFuture(reauthenticate_future, "User::Reauthenticate()", kAuthErrorNone); // Test User::SendEmailVerification(). Future send_email_verification_future = - email_user->SendEmailVerification(); + email_user.SendEmailVerification(); WaitForFuture(send_email_verification_future, "User::SendEmailVerification()", kAuthErrorNone); } @@ -694,29 +1255,123 @@ extern "C" int common_main(int argc, const char* argv[]) { // Test User::Delete(). const std::string new_email_for_delete = CreateNewEmail(); - Future create_future_for_delete = + Future create_future_for_delete = auth->CreateUserWithEmailAndPassword(new_email_for_delete.c_str(), kTestPassword); WaitForSignInFuture( create_future_for_delete, "Auth::CreateUserWithEmailAndPassword() new email for delete", kAuthErrorNone, auth); - User* email_user_for_delete = create_future_for_delete.result() - ? *create_future_for_delete.result() - : nullptr; - if (email_user_for_delete != nullptr) { - Future delete_future = email_user_for_delete->Delete(); + User email_user_for_delete = create_future_for_delete.result() + ? create_future_for_delete.result()->user + : User(); + if (email_user_for_delete.is_valid()) { + Future delete_future = email_user_for_delete.Delete(); WaitForFuture(delete_future, "User::Delete()", kAuthErrorNone); } } { // We end with a login so that we can test if a second run will detect // that we're already logged-in. - Future sign_in_future = auth->SignInAnonymously(); + Future sign_in_future = auth->SignInAnonymously(); WaitForSignInFuture(sign_in_future, "Auth::SignInAnonymously() at end", kAuthErrorNone, auth); + + LogMessage("Anonymous uid(%s)", auth->current_user().uid().c_str()); } +#ifdef INTERNAL_EXPERIMENTAL +#if defined TARGET_OS_IPHONE || defined(__ANDROID__) + // --- FederatedAuthProvider tests ------------------------------------------ + { + { // --- LinkWithProvider --- + LogMessage("LinkWithProvider"); + UserLogin user_login(auth); // Generate a random name/password + user_login.Register(); + if (!user_login.user()) { + LogMessage("ERROR: Could not register new user."); + } else { + LogMessage("Setting up provider data"); + firebase::auth::FederatedOAuthProviderData provider_data; + provider_data.provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + provider_data.provider_id = "google.com"; + provider_data.scopes = { + "https://www.googleapis.com/auth/fitness.activity.read"}; + provider_data.custom_parameters = {{"req_id", "1234"}}; + + LogMessage("Configuration oAuthProvider"); + firebase::auth::FederatedOAuthProvider provider; + provider.SetProviderData(provider_data); + LogMessage("invoking linkwithprovider"); + Future sign_in_future = + user_login.user().LinkWithProvider(&provider); + WaitForSignInFuture(sign_in_future, "LinkWithProvider", kAuthErrorNone, + auth); + if (sign_in_future.error() == kAuthErrorNone) { + const AuthResult* result_ptr = sign_in_future.result(); + LogMessage("user email %s", result_ptr->user.email().c_str()); + LogMessage("Additonal user info provider_id: %s", + result_ptr->info.provider_id.c_str()); + LogMessage("LinkWithProviderDone"); + } + } + } + + { + LogMessage("SignInWithProvider"); + // --- SignInWithProvider --- + firebase::auth::FederatedOAuthProviderData provider_data; + provider_data.provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + provider_data.custom_parameters = {{"req_id", "1234"}}; + + firebase::auth::FederatedOAuthProvider provider; + provider.SetProviderData(provider_data); + LogMessage("SignInWithProvider SETUP COMPLETE"); + Future sign_in_future = auth->SignInWithProvider(&provider); + WaitForSignInFuture(sign_in_future, "SignInWithProvider", kAuthErrorNone, + auth); + if (sign_in_future.error() == kAuthErrorNone && + sign_in_future.result() != nullptr) { + LogAuthResult(*sign_in_future.result()); + } + } + + { // --- ReauthenticateWithProvider --- + LogMessage("ReauthethenticateWithProvider"); + if (!auth->current_user()) { + LogMessage("ERROR: Expected User from SignInWithProvider"); + } else { + firebase::auth::FederatedOAuthProviderData provider_data; + provider_data.provider_id = + firebase::auth::GoogleAuthProvider::kProviderId; + provider_data.custom_parameters = {{"req_id", "1234"}}; + + firebase::auth::FederatedOAuthProvider provider; + provider.SetProviderData(provider_data); + Future sign_in_future = + auth->current_user().ReauthenticateWithProvider(&provider); + WaitForSignInFuture(sign_in_future, "ReauthenticateWithProvider", + kAuthErrorNone, auth); + if (sign_in_future.error() == kAuthErrorNone && + sign_in_future.result() != nullptr) { + LogAuthResult(*sign_in_future.result()); + } + } + } + + // Clean up provider-linked user so we can run the test app again + // and not get "user with that email already exists" errors. + if (auth->current_user()) { + WaitForFuture(auth->current_user().Delete(), "Delete User", + kAuthErrorNone, + /*log_error=*/true); + } + } // end FederatedAuthProvider +#endif // TARGET_OS_IPHONE || defined(__ANDROID__) +#endif // INTERNAL_EXPERIMENTAL + LogMessage("Completed Auth tests."); while (!ProcessEvents(1000)) { diff --git a/auth/testapp/src/desktop/desktop_main.cc b/auth/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/auth/testapp/src/desktop/desktop_main.cc +++ b/auth/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/auth/testapp/src/ios/ios_main.mm b/auth/testapp/src/ios/ios_main.mm index 6ccb2de5..16ede7ef 100644 --- a/auth/testapp/src/ios/ios_main.mm +++ b/auth/testapp/src/ios/ios_main.mm @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#import #import #include @@ -28,6 +29,8 @@ @interface AppDelegate : UIResponder @interface FTAViewController : UIViewController +@property(atomic, strong) NSString *textEntryResult; + @end static int g_exit_status = 0; @@ -36,6 +39,30 @@ @interface FTAViewController : UIViewController static NSCondition *g_shutdown_signal; static UITextView *g_text_view; static UIView *g_parent_view; +static FTAViewController *g_view_controller; + +void initGameCenter(UIViewController* view_controller) { + if (![GKLocalPlayer class]) + return; + + __weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; + localPlayer.authenticateHandler = ^(UIViewController *gcAuthViewController, NSError *error) { + if (gcAuthViewController != nil) { + // Pause any activities that require user interaction, then present the + // gcAuthViewController to the player. + [view_controller presentViewController:gcAuthViewController animated:YES completion:nil]; + } else if (localPlayer.isAuthenticated) { + // Player is already logged into Game Center + } else { + if (error) { + LogMessage("Unable to initialize GameCenter: %s", error.localizedDescription); + return; + } else { + LogMessage("Unable to initialize GameCenter: Unknown Error"); + } + } + }; +} @implementation FTAViewController @@ -45,6 +72,7 @@ - (void)viewDidLoad { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ const char *argv[] = {FIREBASE_TESTAPP_NAME}; [g_shutdown_signal lock]; + initGameCenter(self); g_exit_status = common_main(1, argv); [g_shutdown_complete signal]; }); @@ -86,6 +114,56 @@ int main(int argc, char* argv[]) { return g_exit_status; } +// Create an alert dialog via UIAlertController, and prompt the user to enter a line of text. +// This function spins until the text has been entered (or the alert dialog was canceled). +// If the user cancels, returns an empty string. +std::string ReadTextInput(const char *title, const char *message, const char *placeholder) { + assert(g_view_controller); + // This should only be called from a background thread, as it blocks, which will mess up the main + // thread. + assert(![NSThread isMainThread]); + + g_view_controller.textEntryResult = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + UIAlertController *alertController = + [UIAlertController alertControllerWithTitle:@(title) + message:@(message) + preferredStyle:UIAlertControllerStyleAlert]; + [alertController addTextFieldWithConfigurationHandler:^(UITextField *_Nonnull textField) { + textField.placeholder = @(placeholder); + }]; + UIAlertAction *confirmAction = [UIAlertAction + actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *_Nonnull action) { + g_view_controller.textEntryResult = alertController.textFields.firstObject.text; + }]; + [alertController addAction:confirmAction]; + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *_Nonnull action) { + g_view_controller.textEntryResult = @""; + }]; + [alertController addAction:cancelAction]; + [g_view_controller presentViewController:alertController animated:YES completion:nil]; + }); + + while (true) { + // Pause a second, waiting for the user to enter text. + if (ProcessEvents(1000)) { + // If this returns true, an exit was requested. + return ""; + } + if (g_view_controller.textEntryResult != nil) { + // textEntryResult will be set to non-nil when a dialog button is clicked. + std::string result = g_view_controller.textEntryResult.UTF8String; + g_view_controller.textEntryResult = nil; // Consume the result. + return result; + } + } +} + @implementation AppDelegate - (BOOL)application:(UIApplication*)application @@ -95,22 +173,23 @@ - (BOOL)application:(UIApplication*)application [g_shutdown_complete lock]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - FTAViewController *viewController = [[FTAViewController alloc] init]; - self.window.rootViewController = viewController; + g_view_controller = [[FTAViewController alloc] init]; + self.window.rootViewController = g_view_controller; [self.window makeKeyAndVisible]; - g_text_view = [[UITextView alloc] initWithFrame:viewController.view.bounds]; + g_text_view = [[UITextView alloc] initWithFrame:g_view_controller.view.bounds]; g_text_view.editable = NO; g_text_view.scrollEnabled = YES; g_text_view.userInteractionEnabled = YES; - [viewController.view addSubview:g_text_view]; + [g_view_controller.view addSubview:g_text_view]; return YES; } - (void)applicationWillTerminate:(UIApplication *)application { + g_view_controller = nil; g_shutdown = true; [g_shutdown_signal signal]; [g_shutdown_complete wait]; diff --git a/auth/testapp/src/main.h b/auth/testapp/src/main.h index 2eda2c10..130286c1 100644 --- a/auth/testapp/src/main.h +++ b/auth/testapp/src/main.h @@ -15,14 +15,17 @@ #ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT #define FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#include + #if defined(__ANDROID__) #include #include #elif defined(__APPLE__) +#include extern "C" { #include } // extern "C" -#endif // __ANDROID__ +#endif // __ANDROID__, __APPLE__ // Defined using -DANDROID_MAIN_APP_NAME=some_app_name when compiling this // file. @@ -42,7 +45,7 @@ bool ProcessEvents(int msec); // (and usage) vary based on the OS. #if defined(__ANDROID__) typedef jobject WindowContext; // A jobject to the Java Activity. -#elif defined(__APPLE__) +#elif TARGET_OS_IPHONE typedef id WindowContext; // A pointer to an iOS UIView. #else typedef void* WindowContext; // A void* for any other environments. @@ -60,4 +63,11 @@ jobject GetActivity(); // to the root view of the view controller. WindowContext GetWindowContext(); +// Prompt the user with a dialog box to enter a line of text, blocking +// until the user enters the text or the dialog box is canceled. +// Returns the text that was entered, or an empty string if the user +// canceled. +std::string ReadTextInput(const char* title, const char* message, + const char* placeholder); + #endif // FIREBASE_TESTAPP_MAIN_H_ // NOLINT diff --git a/auth/testapp/testapp.xcodeproj/project.pbxproj b/auth/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..dbad8094 100644 --- a/auth/testapp/testapp.xcodeproj/project.pbxproj +++ b/auth/testapp/testapp.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 529227241C85FB7600C89379 /* ios_main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 529227221C85FB7600C89379 /* ios_main.mm */; }; 52B71EBB1C8600B600398745 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52B71EBA1C8600B600398745 /* Images.xcassets */; }; D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; + D6FC32B32C06595E00E3E028 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6FC32B22C06595E00E3E028 /* GameKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -30,6 +31,7 @@ 52B71EBA1C8600B600398745 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testapp/Images.xcassets; sourceTree = ""; }; 52FD1FF81C85FFA000BC68E3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = testapp/Info.plist; sourceTree = ""; }; D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; + D6FC32B22C06595E00E3E028 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = System/Library/Frameworks/GameKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -38,6 +40,7 @@ buildActionMask = 2147483647; files = ( 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + D6FC32B32C06595E00E3E028 /* GameKit.framework in Frameworks */, 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, ); @@ -46,6 +49,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3E2FC86EB58D8B5977042124 /* Pods */ = { + isa = PBXGroup; + children = ( + ); + path = Pods; + sourceTree = ""; + }; 529226C91C85F68000C89379 = { isa = PBXGroup; children = ( @@ -56,6 +66,7 @@ 5292271D1C85FB5500C89379 /* src */, 529226D41C85F68000C89379 /* Frameworks */, 529226D31C85F68000C89379 /* Products */, + 3E2FC86EB58D8B5977042124 /* Pods */, ); sourceTree = ""; }; @@ -70,6 +81,7 @@ 529226D41C85F68000C89379 /* Frameworks */ = { isa = PBXGroup; children = ( + D6FC32B22C06595E00E3E028 /* GameKit.framework */, 529226D51C85F68000C89379 /* Foundation.framework */, 529226D71C85F68000C89379 /* CoreGraphics.framework */, 529226D91C85F68000C89379 /* UIKit.framework */, @@ -135,6 +147,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 529226C91C85F68000C89379; @@ -208,7 +221,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +258,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/auth/testapp/testapp/Info.plist b/auth/testapp/testapp/Info.plist index cd039b8f..3831d56d 100644 --- a/auth/testapp/testapp/Info.plist +++ b/auth/testapp/testapp/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.google.ios.auth.testapp + com.google.FirebaseCppAuthTestApp.dev CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -16,10 +16,18 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleURLTypes + + CFBundleTypeRole + Editor + CFBundleURLName + com.google.FirebaseCppAuthTestApp.dev + CFBundleURLSchemes + + com.google.FirebaseCppAuthTestApp.dev + + CFBundleTypeRole Editor @@ -27,19 +35,9 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID - - CFBundleTypeRole - Editor - CFBundleURLName - google - CFBundleURLSchemes - - com.google.ios.auth.testapp - - CFBundleVersion 1 diff --git a/database/testapp/AndroidManifest.xml b/database/testapp/AndroidManifest.xml index da5edbaa..f94a8ca5 100644 --- a/database/testapp/AndroidManifest.xml +++ b/database/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + ) + 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_database firebase_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/database/testapp/Podfile b/database/testapp/Podfile index 53076d26..5832b272 100644 --- a/database/testapp/Podfile +++ b/database/testapp/Podfile @@ -1,7 +1,8 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # Firebase Realtime Database test application. target 'testapp' do -pod 'Firebase/Database' -pod 'Firebase/Auth' + pod 'Firebase/Database', '10.25.0' + pod 'Firebase/Auth', '10.25.0' end diff --git a/database/testapp/build.gradle b/database/testapp/build.gradle index ef9ac466..bfa91283 100644 --- a/database/testapp/build.gradle +++ b/database/testapp/build.gradle @@ -2,118 +2,76 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.firebase.cpp.database.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.firebase.cpp.database.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/auth.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/database.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile 'com.google.firebase:firebase-auth:10.2.0' - compile 'com.google.firebase:firebase-database:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + database } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/database/testapp/gradle.properties b/database/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/database/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/database/testapp/gradle/wrapper/gradle-wrapper.properties b/database/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/database/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/database/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/database/testapp/jni/Android.mk b/database/testapp/jni/Android.mk deleted file mode 100644 index 2b62b147..00000000 --- a/database/testapp/jni/Android.mk +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_auth -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libauth.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_database -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libdatabase.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_database \ - firebase_auth \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/database/testapp/jni/Application.mk b/database/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/database/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/database/testapp/proguard.pro b/database/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/database/testapp/proguard.pro +++ b/database/testapp/proguard.pro @@ -1 +1,2 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/database/testapp/readme.md b/database/testapp/readme.md index 809e0dc5..7841f590 100644 --- a/database/testapp/readme.md +++ b/database/testapp/readme.md @@ -44,16 +44,16 @@ Building and Running the testapp - Link your iOS app to the Firebase libraries. - Get CocoaPods version 1 or later by running, ``` - $ sudo gem install CocoaPods --pre + sudo gem install cocoapods --pre ``` - From the testapp directory, install the CocoaPods listed in the Podfile by running, ``` - $ pod install + pod install ``` - Open the generated Xcode workspace (which now has the CocoaPods), ``` - $ open testapp.xcworkspace + open testapp.xcworkspace ``` - For further details please refer to the [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). @@ -70,8 +70,8 @@ Building and Running the testapp authenticate with Firebase Database, which requires a signed-in user by default (an anonymous user will suffice). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework - frameworks/ios/universal/firebase_auth.framework @@ -125,18 +125,19 @@ Building and Running the testapp - For further details please refer to the [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Configure the location of the Firebase C++ SDK by setting the firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For example, in the project directory: ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties ``` - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. - Open *build.gradle* in Android Studio. - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. @@ -146,6 +147,71 @@ Building and Running the testapp viewed on the device's display, or in the logcat output of Android studio or by running "adb logcat *:W android_main firebase" from the command line. +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - The Desktop version of the library requires you to set up any keys you + use via Query::OrderByChild() in advance. Add this to your database's + rules in the _Rules_ tab of your database settings in Firebase console: + ``` + { + "rules": { + // Require authentication for writing to the database. + ".read": "true", + ".write": "auth != null", + // Set up entity_list to be indexed by the entity_type child. + "test_app_data": { + "$dummy": { + "ChildListener": { + "entity_list": { + ".indexOn": "entity_type" + } + } + } + } + } + } + ``` + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. + Known issues ------------ @@ -159,7 +225,7 @@ Known issues Support ------- -https://firebase.google.com/support/ +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -180,4 +246,3 @@ 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. - diff --git a/database/testapp/settings.gradle b/database/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/database/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/database/testapp/src/common_main.cc b/database/testapp/src/common_main.cc index 18c66a68..3a35179b 100644 --- a/database/testapp/src/common_main.cc +++ b/database/testapp/src/common_main.cc @@ -52,7 +52,7 @@ class SampleValueListener : public firebase::database::ValueListener { std::vector seen_values_; }; -// An example ChildListener class. TODO(jsimantov) implement. +// An example ChildListener class. class SampleChildListener : public firebase::database::ChildListener { public: void OnChildAdded(const firebase::database::DataSnapshot& snapshot, @@ -109,6 +109,9 @@ class ExpectValueListener : public firebase::database::ValueListener { const firebase::database::DataSnapshot& snapshot) override { if (snapshot.value().AsString() == wait_value_) { got_value_ = true; + } else { + LogMessage( + "FAILURE: ExpectValueListener did not receive the expected result."); } } void OnCancelled(const firebase::database::Error& error_code, @@ -189,12 +192,14 @@ extern "C" int common_main(int argc, const char* argv[]) { } LogMessage("Successfully initialized Firebase Auth and Firebase Database."); + database->set_persistence_enabled(true); + // Sign in using Auth before accessing the database. // The default Database permissions allow anonymous users access. This will // work as long as your project's Authentication permissions allow anonymous // signin. { - firebase::Future sign_in_future = + firebase::Future sign_in_future = auth->SignInAnonymously(); WaitForCompletion(sign_in_future, "SignInAnonymously"); if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { @@ -214,8 +219,8 @@ extern "C" int common_main(int argc, const char* argv[]) { std::string saved_url; // persists across connections // Create a unique child in the database that we can run our tests in. - firebase::database::DatabaseReference ref = - database->GetReference("test_app_data").PushChild(); + firebase::database::DatabaseReference ref; + ref = database->GetReference("test_app_data").PushChild(); saved_url = ref.url(); LogMessage("URL: %s", saved_url.c_str()); @@ -226,6 +231,7 @@ extern "C" int common_main(int argc, const char* argv[]) { { const char* kSimpleString = "Some simple string"; const int kSimpleInt = 2; + const int kSimplePriority = 100; const double kSimpleDouble = 3.4; const bool kSimpleBool = true; @@ -243,22 +249,30 @@ extern "C" int common_main(int argc, const char* argv[]) { ref.Child("Simple") .Child("Timestamp") .SetValue(firebase::database::ServerTimestamp()); + firebase::Future f6 = + ref.Child("Simple") + .Child("IntAndPriority") + .SetValueAndPriority(kSimpleInt, kSimplePriority); WaitForCompletion(f1, "SetSimpleString"); WaitForCompletion(f2, "SetSimpleInt"); WaitForCompletion(f3, "SetSimpleDouble"); WaitForCompletion(f4, "SetSimpleBool"); WaitForCompletion(f5, "SetSimpleTimestamp"); + WaitForCompletion(f6, "SetSimpleIntAndPriority"); if (f1.error() != firebase::database::kErrorNone || f2.error() != firebase::database::kErrorNone || f3.error() != firebase::database::kErrorNone || f4.error() != firebase::database::kErrorNone || - f5.error() != firebase::database::kErrorNone) { + f5.error() != firebase::database::kErrorNone || + f6.error() != firebase::database::kErrorNone) { LogMessage("ERROR: Set simple values failed."); LogMessage(" String: Error %d: %s", f1.error(), f1.error_message()); LogMessage(" Int: Error %d: %s", f2.error(), f2.error_message()); LogMessage(" Double: Error %d: %s", f3.error(), f3.error_message()); LogMessage(" Bool: Error %d: %s", f4.error(), f4.error_message()); LogMessage(" Timestamp: Error %d: %s", f5.error(), f5.error_message()); + LogMessage(" Int and Priority: Error %d: %s", f6.error(), + f6.error_message()); } else { LogMessage("SUCCESS: Set simple values."); } @@ -277,30 +291,36 @@ extern "C" int common_main(int argc, const char* argv[]) { ref.Child("Simple").Child("Bool").GetValue(); firebase::Future f5 = ref.Child("Simple").Child("Timestamp").GetValue(); + firebase::Future f6 = + ref.Child("Simple").Child("IntAndPriority").GetValue(); WaitForCompletion(f1, "GetSimpleString"); WaitForCompletion(f2, "GetSimpleInt"); WaitForCompletion(f3, "GetSimpleDouble"); WaitForCompletion(f4, "GetSimpleBool"); WaitForCompletion(f5, "GetSimpleTimestamp"); + WaitForCompletion(f6, "GetSimpleIntAndPriority"); if (f1.error() == firebase::database::kErrorNone && f2.error() == firebase::database::kErrorNone && f3.error() == firebase::database::kErrorNone && f4.error() == firebase::database::kErrorNone && - f5.error() == firebase::database::kErrorNone) { + f5.error() == firebase::database::kErrorNone && + f6.error() == firebase::database::kErrorNone) { // Get the current time to compare to the Timestamp. int64_t current_time_milliseconds = static_cast(time(nullptr)) * 1000L; int64_t time_difference = f5.result()->value().AsInt64().int64_value() - current_time_milliseconds; - // As long as our timestamp is within a day, it's correct enough for our - // purposes. - const int64_t kAllowedTimeDifferenceMilliseconds = - 1000L * 60L * 60L * 24L; + // As long as our timestamp is within 15 minutes, it's correct enough + // for our purposes. + const int64_t kAllowedTimeDifferenceMilliseconds = 1000L * 60L * 15L; + if (f1.result()->value().AsString() != kSimpleString || f2.result()->value().AsInt64() != kSimpleInt || f3.result()->value().AsDouble() != kSimpleDouble || f4.result()->value().AsBool() != kSimpleBool || + f6.result()->value().AsInt64() != kSimpleInt || + f6.result()->priority().AsInt64() != kSimplePriority || time_difference > kAllowedTimeDifferenceMilliseconds || time_difference < -kAllowedTimeDifferenceMilliseconds) { LogMessage("ERROR: Get simple values failed, values did not match."); @@ -318,6 +338,12 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage(" Timestamp: Got %lld, expected something near %lld", f5.result()->value().AsInt64().int64_value(), current_time_milliseconds); + LogMessage( + " IntAndPriority: Got {.value:%lld,.priority:%lld}, expected " + "{.value:%d,.priority:%d}", + f6.result()->value().AsInt64().int64_value(), + f6.result()->priority().AsInt64().int64_value(), kSimpleInt, + kSimplePriority); } else { LogMessage("SUCCESS: Get simple values."); @@ -344,6 +370,65 @@ extern "C" int common_main(int argc, const char* argv[]) { } } +#if defined(__ANDROID__) || TARGET_OS_IPHONE + // Actually shut down the realtime database, and restart it, to make sure + // that persistence persists across database object instances. + { + // Write a value that we can test for. + const char* kPersistenceString = "Persistence Test!"; + WaitForCompletion(ref.Child("PersistenceTest").SetValue(kPersistenceString), + "SetPersistenceTestValue"); + + LogMessage("Destroying database object."); + delete database; + LogMessage("Recreating database object."); + database = ::firebase::database::Database::GetInstance(app); + + // Offline mode. If persistence works, we should still be able to fetch + // our value even though we're offline. + + database->GoOffline(); + ref = database->GetReferenceFromUrl(saved_url.c_str()); + + { + LogMessage( + "TEST: Fetching the value while offline via AddValueListener."); + ExpectValueListener* listener = + new ExpectValueListener(kPersistenceString); + ref.Child("PersistenceTest").AddValueListener(listener); + + while (!listener->got_value()) { + ProcessEvents(100); + } + delete listener; + listener = nullptr; + } + + { + LogMessage("TEST: Fetching the value while offline via GetValue."); + firebase::Future value_future = + ref.Child("PersistenceTest").GetValue(); + + WaitForCompletion(value_future, "GetValue"); + + const firebase::database::DataSnapshot& result = *value_future.result(); + + if (value_future.error() == firebase::database::kErrorNone) { + if (result.value().AsString() == kPersistenceString) { + LogMessage("SUCCESS: GetValue returned the correct value."); + } else { + LogMessage("FAILURE: GetValue returned an incorrect value."); + } + } else { + LogMessage("FAILURE: GetValue Future returned an error."); + } + } + + LogMessage("Going back online."); + database->GoOnline(); + } +#endif // defined(__ANDROID__) || TARGET_OS_IPHONE + // Test running a transaction. This will call RunTransaction and set // some values, including incrementing the player's score. { @@ -358,18 +443,26 @@ extern "C" int common_main(int argc, const char* argv[]) { "SetInitialScoreValue"); // The transaction will set the player's item and class, and increment // their score by 100 points. + int score_delta = 100; transaction_future = ref.Child("TransactionResult") - .RunTransaction([](firebase::database::MutableData* data) { - LogMessage(" Transaction function executing."); - data->Child("player_item").set_value("Fire sword"); - data->Child("player_class").set_value("Warrior"); - // Increment the current score by 100. - int64_t score = - data->Child("player_score").value().AsInt64().int64_value(); - data->Child("player_score").set_value(score + kAddedScore); - return firebase::database::kTransactionResultSuccess; - }); + .RunTransaction( + [](firebase::database::MutableData* data, + void* score_delta_void) { + LogMessage(" Transaction function executing."); + data->Child("player_item").set_value("Fire sword"); + data->Child("player_class").set_value("Warrior"); + // Increment the current score by 100. + int64_t score = data->Child("player_score") + .value() + .AsInt64() + .int64_value(); + data->Child("player_score") + .set_value(score + + *reinterpret_cast(score_delta_void)); + return firebase::database::kTransactionResultSuccess; + }, + &score_delta); WaitForCompletion(transaction_future, "RunTransaction"); // Check whether the transaction succeeded, was aborted, or failed with an @@ -585,17 +678,20 @@ extern "C" int common_main(int argc, const char* argv[]) { } // Test a ValueListener, which sits on a Query and listens for changes in - // the - // value at that location. + // the value at that location. { LogMessage("TEST: ValueListener"); SampleValueListener* listener = new SampleValueListener(); - // Set a value before attaching the listener. The listener should not - // receive this value. WaitForCompletion(ref.Child("ValueListener").SetValue(0), "SetValueZero"); // Attach the listener, then set 3 values, which will trigger the // listener. ref.Child("ValueListener").AddValueListener(listener); + + // The listener's OnChanged callback is triggered once when the listener is + // attached and again every time the data, including children, changes. + // Wait for here for a moment for the initial values to be received. + ProcessEvents(2000); + WaitForCompletion(ref.Child("ValueListener").SetValue(1), "SetValueOne"); WaitForCompletion(ref.Child("ValueListener").SetValue(2), "SetValueTwo"); WaitForCompletion(ref.Child("ValueListener").SetValue(3), "SetValueThree"); @@ -614,10 +710,11 @@ extern "C" int common_main(int argc, const char* argv[]) { // Wait a few more seconds to ensure the listener is not triggered. ProcessEvents(2000); - // Ensure that the listener was only triggered 3 times, with the values - // 1, 2, and 3. - if (listener->num_seen_values() == 3 && listener->seen_value(1) && - listener->seen_value(2) && listener->seen_value(3)) { + // Ensure that the listener was only triggered 4 times, with the values + // 0 (the initial value), 1, 2, and 3. + if (listener->num_seen_values() == 4 && listener->seen_value(0) && + listener->seen_value(1) && listener->seen_value(2) && + listener->seen_value(3)) { LogMessage("SUCCESS: ValueListener got all values."); } else { LogMessage("ERROR: ValueListener did not get all values."); @@ -625,9 +722,8 @@ extern "C" int common_main(int argc, const char* argv[]) { delete listener; } - // Test a ChildListener, which sits on a Query and listens for changes in - // the child heirarchy at the location. + // the child hierarchy at the location. { LogMessage("TEST: ChildListener"); SampleChildListener* listener = new SampleChildListener(); @@ -639,6 +735,11 @@ extern "C" int common_main(int argc, const char* argv[]) { .EqualTo("enemy") .AddChildListener(listener); + // The listener's OnChanged callback is triggered once when the listener is + // attached and again every time the data, including children, changes. + // Wait for here for a moment for the initial values to be received. + ProcessEvents(2000); + std::map params; params["entity_name"] = "cobra"; params["entity_type"] = "enemy"; @@ -847,6 +948,7 @@ extern "C" int common_main(int argc, const char* argv[]) { ref.Child("OnDisconnectTests").GetValue(); WaitForCompletion(future, "ReadOnDisconnectChanges"); bool failed = false; + if (future.error() == firebase::database::kErrorNone) { const firebase::database::DataSnapshot& result = *future.result(); if (!result.HasChild("SetValueTo1") || @@ -901,10 +1003,39 @@ extern "C" int common_main(int argc, const char* argv[]) { future.error(), future.error_message()); } + bool test_snapshot_was_valid = false; + firebase::database::DataSnapshot* test_snapshot = nullptr; + if (future.error() == firebase::database::kErrorNone) { + // This is a little convoluted as it's not possible to construct an + // empty test snapshot so we copy the result and point at the copy. + static firebase::database::DataSnapshot copied_snapshot = // NOLINT + *future.result(); // NOLINT + test_snapshot = &copied_snapshot; + test_snapshot_was_valid = test_snapshot->is_valid(); + } + LogMessage("Shutdown the Database library."); delete database; database = nullptr; + // Ensure that the ref we had is now invalid. + if (!ref.is_valid()) { + LogMessage("SUCCESS: Reference was invalidated on library shutdown."); + } else { + LogMessage("ERROR: Reference is still valid after library shutdown."); + } + + if (test_snapshot_was_valid && test_snapshot) { + if (!test_snapshot->is_valid()) { + LogMessage("SUCCESS: Snapshot was invalidated on library shutdown."); + } else { + LogMessage("ERROR: Snapshot is still valid after library shutdown."); + } + } else { + LogMessage( + "WARNING: Snapshot was already invalid at shutdown, couldn't check."); + } + LogMessage("Signing out from anonymous account."); auth->SignOut(); LogMessage("Shutdown the Auth library."); diff --git a/database/testapp/src/desktop/desktop_main.cc b/database/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/database/testapp/src/desktop/desktop_main.cc +++ b/database/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/database/testapp/testapp.xcodeproj/project.pbxproj b/database/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/database/testapp/testapp.xcodeproj/project.pbxproj +++ b/database/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/database/testapp/testapp/Info.plist b/database/testapp/testapp/Info.plist index 12137455..095e6672 100644 --- a/database/testapp/testapp/Info.plist +++ b/database/testapp/testapp/Info.plist @@ -16,8 +16,6 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleURLTypes @@ -27,7 +25,7 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID diff --git a/firestore/.clang-format b/firestore/.clang-format new file mode 100644 index 00000000..e2412319 --- /dev/null +++ b/firestore/.clang-format @@ -0,0 +1,9 @@ +BasedOnStyle: Google +Standard: Cpp11 +ColumnLimit: 80 +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true +SpacesInContainerLiterals: true +DerivePointerAlignment: false +PointerAlignment: Left +IncludeBlocks: Preserve diff --git a/firestore/testapp/AndroidManifest.xml b/firestore/testapp/AndroidManifest.xml new file mode 100644 index 00000000..b4f84579 --- /dev/null +++ b/firestore/testapp/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/firestore/testapp/CMakeLists.txt b/firestore/testapp/CMakeLists.txt new file mode 100644 index 00000000..1a2c953f --- /dev/null +++ b/firestore/testapp/CMakeLists.txt @@ -0,0 +1,136 @@ +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase samples. +# 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() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +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() + +# 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) + +# Sample source files. +set(FIREBASE_SAMPLE_COMMON_SRCS + src/main.h + src/common_main.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Sample uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_SAMPLE_ANDROID_SRCS + src/android/android_main.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. + # This also does a workaround that prevents numerous errors that occur when + # building using Android Studio. The errors look like: + # libfirebase_auth.a(auth.o): relocation R_386_GOTOFF against preemptible + # symbol cannot be used when making a shared object. + # Taken from: + # https://github.com/opencv/opencv/issues/10229#issuecomment-359202825 + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate \ + -Wl,--exclude-libs,libfirebase_firestore.a \ + -Wl,--exclude-libs,libfirebase_auth.a \ + -Wl,--exclude-libs,libfirebase_app.a" + ) + + # Define the target as a shared library, as that is what gradle expects. + set(target_name "android_main") + add_library(${target_name} SHARED + ${FIREBASE_SAMPLE_ANDROID_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + target_link_libraries(${target_name} + log android atomic native_app_glue + ) + + target_include_directories(${target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS) +else() + # Build a desktop application. + + # 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 sample. + set(FIREBASE_SAMPLE_DESKTOP_SRCS + src/desktop/desktop_main.cc + ) + + set(target_name "desktop_testapp") + add_executable(${target_name} + ${FIREBASE_SAMPLE_DESKTOP_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS + gssapi_krb5 + pthread + "-framework CoreFoundation" + "-framework Foundation" + "-framework GSS" + "-framework Security" + "-framework SystemConfiguration" + ) + elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 iphlpapi psapi userenv dbghelp bcrypt) + 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 ${config}) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${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_firestore firebase_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/admob/testapp/LaunchScreen.storyboard b/firestore/testapp/LaunchScreen.storyboard similarity index 100% rename from admob/testapp/LaunchScreen.storyboard rename to firestore/testapp/LaunchScreen.storyboard diff --git a/firestore/testapp/Podfile b/firestore/testapp/Podfile new file mode 100644 index 00000000..e0bca1be --- /dev/null +++ b/firestore/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# Firebase Firestore test application. +target 'testapp' do + pod 'Firebase/Firestore', '10.25.0' + pod 'Firebase/Auth', '10.25.0' +end diff --git a/firestore/testapp/README.md b/firestore/testapp/README.md new file mode 100644 index 00000000..500c24c6 --- /dev/null +++ b/firestore/testapp/README.md @@ -0,0 +1,268 @@ +# Firebase Firestore Quickstart + +The Firebase Firestore Test Application (testapp) demonstrates Firebase +Firestore operations with the Firebase Firestore C++ SDK. The application has no +user interface and simply logs actions it's performing to the console. + +The testapp performs the following: + +- Creates a firebase::App in a platform-specific way. The App holds + platform-specific context that's used by other Firebase APIs, and is a + central point for communication between the Firebase Firestore C++ and + Firebase Auth C++ libraries. +- Gets a pointer to firebase::Auth, and signs in anonymously. This allows the + testapp to access a Firebase Firestore instance with authentication rules + enabled. +- Initializes a Firestore instance and sets its logging level to + `kLogLevelDebug` in order to see debug messages in the logs. +- Tests that it can create `Timestamp`, `SnapshotMetadata`, and `GeoPoint` + objects. +- Creates a collection and a document inside that collection. +- Writes initial data to the document (`Set`), updates the document content + (`Update`), reads the document back (`Get`), and checks that the contents + match our expectation. +- Deletes the document. +- Performs a batch write to two documents. +- Performs a Transaction containing three operations (`Update`, `Delete`, and + `Set`) on three different documents. +- Queries documents in the collection that match a certain condition. Ensures + the documents returned via the query match our expectation. + +Introduction +------------ + +- [Read more about Firebase Firestore](https://firebase.google.com/docs/firestore/) + +Building and Running the testapp +-------------------------------- + +### iOS + +- Link your iOS app to the Firebase libraries. + + - Get CocoaPods version 1 or later by running, + + ``` + sudo gem install cocoapods --pre + ``` + + - Update the pod versions in the Podfile to match the C++ SDK version that you are using. + For the latest version of the C++ SDK, you can find the associated pod versions on the + [`Add Firebase to your project` page](https://firebase.google.com/docs/cpp/setup?platform=ios#libraries-ios). + For instance: if you're using SDK version 8.2.0, use the following in the Podfile + (but note that pod versions may not always match the C++ SDK version): + + ``` + pod 'Firebase/Firestore', '8.2.0' + pod 'Firebase/Auth', '8.2.0' + ``` + + - From the testapp directory, install the CocoaPods listed in the Podfile + by running, + + ``` + pod install + ``` + + - Open the generated Xcode workspace (which now has the CocoaPods), + + ``` + open testapp.xcworkspace + ``` + + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + +- Register your iOS app with Firebase. + + - Create a new app on the + [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.firestore.testapp" as the iOS + Bundle ID while you're testing. You can omit App Store ID while + testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase + console to the testapp root directory. This file identifies your iOS app + to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the testapp to use anonymous sign-in to + authenticate with Firebase Firestore, which requires a signed-in user by + default (an anonymous user will suffice). + +- Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + +- Add the following frameworks from the Firebase C++ SDK to the project: + + - xcframeworks/firebase.xcframework/ios-arm64_armv7/firebase.framework + - xcframeworks/firebase_firestore.xcframework/ios-arm64_armv7/firebase_firestore.framework + - xcframeworks/firebase_auth.xcframework/ios-arm64_armv7/firebase_auth.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, then you would add the path + `/Users/me/firebase_cpp_sdk/xcframeworks/**`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. Select + the "Build Settings" tab, and click "All" to see all the build + settings. Scroll down to "Search Paths", and add your path to + "Framework Search Paths". + +- In XCode, build & run the sample on an iOS device or simulator. + +- The testapp has no interative interface. The output of the app can be viewed + via the console or on the device's display. In Xcode, select "View --> Debug + Area --> Activate Console" from the menu to view the console. + +### Android + +- Register your Android app with Firebase. + + - Create a new app on the + [Firebase console](https://firebase.google.com/console/), and attach + your Android app to it. + + - You can use "com.google.firebase.cpp.firestore.testapp" as the + Package Name while you're testing. + - To + [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + + or this command on Windows, + + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + + - Add the `google-services.json` file that you downloaded from Firebase + console to the root directory of testapp. This file identifies your + Android app to the Firebase backend. + + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the testapp to use anonymous sign-in to + authenticate with Firebase Firestore, which requires a signed-in user by + default (an anonymous user will suffice). + + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + +- Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + +- Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For + example, in the project directory: + + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + +- Ensure the Android SDK and NDK locations are set in Android Studio. + + - From the Android Studio launch menu, go to `File/Project Structure...` + or `Configure/Project Defaults/Project Structure...` (Shortcut: + Control + Alt + Shift + S on windows, Command + ";" on a mac) and + download the SDK and NDK if the locations are not yet set. + +- Open *build.gradle* in Android Studio. + + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + +- Install the SDK Platforms that Android Studio reports missing. + +- Build the testapp and run it on an Android device or emulator. + +- The testapp has no interactive interface. The output of the app can be + viewed on the device's display, or in the logcat output of Android studio or + by running "adb logcat *:W android_main firebase" from the command line. + +### Desktop + +- Register your app with Firebase. + - Create a new app on the + [Firebase console](https://firebase.google.com/console/), following the + above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py + --plist`, located in the Firebase C++ SDK, to convert your + `GoogleService-Info.plist` file into a `google-services-desktop.json` + file, which can then be placed in the root directory of the testapp. +- Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. +- Configure the testapp with the location of the Firebase C++ SDK. This can be + done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path to + the appropriate location. +- From the testapp directory, generate the build files by running, + + ``` + cmake . + ``` + + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + +- Build the testapp, by either opening the generated project file based on the + platform, or running, + + ``` + cmake --build . + ``` + +- Execute the testapp by running, + + ``` + ./desktop_testapp + ``` + + Note that the executable might be under another directory, such as Debug. + +- The testapp has no user interface, but the output can be viewed via the + console. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016-2019 Google LLC Licensed to the Apache Software Foundation (ASF) +under one or more contributor license agreements. See the NOTICE file +distributed with this work for additional information regarding copyright +ownership. The ASF licenses this file to you 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. diff --git a/firestore/testapp/build.gradle b/firestore/testapp/build.gradle new file mode 100644 index 00000000..6adb3b5a --- /dev/null +++ b/firestore/testapp/build.gradle @@ -0,0 +1,78 @@ +// 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' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.firestore.testapp' + minSdkVersion 23 + targetSdkVersion 28 + 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') + } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + firestore +} + +apply plugin: 'com.google.gms.google-services' diff --git a/firestore/testapp/gradle.properties b/firestore/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/firestore/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/admob/testapp/gradle/wrapper/gradle-wrapper.jar b/firestore/testapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from admob/testapp/gradle/wrapper/gradle-wrapper.jar rename to firestore/testapp/gradle/wrapper/gradle-wrapper.jar diff --git a/invites/testapp/gradle/wrapper/gradle-wrapper.properties b/firestore/testapp/gradle/wrapper/gradle-wrapper.properties similarity index 52% rename from invites/testapp/gradle/wrapper/gradle-wrapper.properties rename to firestore/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/invites/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/firestore/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/admob/testapp/gradlew b/firestore/testapp/gradlew similarity index 100% rename from admob/testapp/gradlew rename to firestore/testapp/gradlew diff --git a/admob/testapp/gradlew.bat b/firestore/testapp/gradlew.bat similarity index 100% rename from admob/testapp/gradlew.bat rename to firestore/testapp/gradlew.bat diff --git a/invites/testapp/proguard.pro b/firestore/testapp/proguard.pro similarity index 85% rename from invites/testapp/proguard.pro rename to firestore/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/invites/testapp/proguard.pro +++ b/firestore/testapp/proguard.pro @@ -1 +1,2 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/invites/testapp/res/layout/main.xml b/firestore/testapp/res/layout/main.xml similarity index 100% rename from invites/testapp/res/layout/main.xml rename to firestore/testapp/res/layout/main.xml diff --git a/invites/testapp/res/values/strings.xml b/firestore/testapp/res/values/strings.xml similarity index 52% rename from invites/testapp/res/values/strings.xml rename to firestore/testapp/res/values/strings.xml index 2e7bcd10..25c93dde 100644 --- a/invites/testapp/res/values/strings.xml +++ b/firestore/testapp/res/values/strings.xml @@ -1,4 +1,4 @@ - Firebase Invites Test + Firebase Firestore Test diff --git a/firestore/testapp/settings.gradle b/firestore/testapp/settings.gradle new file mode 100644 index 00000000..5b0e2c37 --- /dev/null +++ b/firestore/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2019 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 ((new 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" \ No newline at end of file diff --git a/admob/testapp/src/android/android_main.cc b/firestore/testapp/src/android/android_main.cc similarity index 100% rename from admob/testapp/src/android/android_main.cc rename to firestore/testapp/src/android/android_main.cc diff --git a/admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/firestore/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java similarity index 100% rename from admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java rename to firestore/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java diff --git a/firestore/testapp/src/common_main.cc b/firestore/testapp/src/common_main.cc new file mode 100644 index 00000000..0de5d39b --- /dev/null +++ b/firestore/testapp/src/common_main.cc @@ -0,0 +1,341 @@ +// Copyright 2016 Google Inc. 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 "firebase/auth.h" +#include "firebase/auth/user.h" +#include "firebase/firestore.h" +#include "firebase/util.h" + +// Thin OS abstraction layer. +#include "main.h" // NOLINT + +const int kTimeoutMs = 5000; +const int kSleepMs = 100; + +// Waits for a Future to be completed and returns whether the future has +// completed successfully. If the Future returns an error, it will be logged. +bool Await(const firebase::FutureBase& future, const char* name) { + int remaining_timeout = kTimeoutMs; + while (future.status() == firebase::kFutureStatusPending && + remaining_timeout > 0) { + remaining_timeout -= kSleepMs; + ProcessEvents(kSleepMs); + } + + if (future.status() != firebase::kFutureStatusComplete) { + LogMessage("ERROR: %s returned an invalid result.", name); + return false; + } else if (future.error() != 0) { + LogMessage("ERROR: %s returned error %d: %s", name, future.error(), + future.error_message()); + return false; + } + return true; +} + +class Countable { + public: + int event_count() const { return event_count_; } + + protected: + int event_count_ = 0; +}; + +template +class TestEventListener : public Countable { + public: + explicit TestEventListener(std::string name) : name_(std::move(name)) {} + + void OnEvent(const T& value, + const firebase::firestore::Error error_code, + const std::string& error_message) { + event_count_++; + if (error_code != firebase::firestore::kErrorOk) { + LogMessage("ERROR: EventListener %s got %d (%s).", name_.c_str(), + error_code, error_message.c_str()); + } + } + + template + firebase::firestore::ListenerRegistration AttachTo(U* ref) { + return ref->AddSnapshotListener( + [this](const T& result, firebase::firestore::Error error_code, + const std::string& error_message) { + OnEvent(result, error_code, error_message); + }); + } + + private: + std::string name_; +}; + +void Await(const Countable& listener, const char* name) { + int remaining_timeout = kTimeoutMs; + while (listener.event_count() && remaining_timeout > 0) { + remaining_timeout -= kSleepMs; + ProcessEvents(kSleepMs); + } + if (remaining_timeout <= 0) { + LogMessage("ERROR: %s listener timed out.", name); + } +} + +extern "C" int common_main(int argc, const char* argv[]) { + firebase::App* app; + +#if defined(__ANDROID__) + app = firebase::App::Create(GetJniEnv(), GetActivity()); +#else + app = firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogMessage("Initialized Firebase App."); + + LogMessage("Initializing Firebase Auth..."); + firebase::InitResult result; + firebase::auth::Auth* auth = firebase::auth::Auth::GetAuth(app, &result); + if (result != firebase::kInitResultSuccess) { + LogMessage("Failed to initialize Firebase Auth, error: %d", + static_cast(result)); + return -1; + } + LogMessage("Initialized Firebase Auth."); + + LogMessage("Signing in..."); + // Auth caches the previously signed-in user, which can be annoying when + // trying to test for sign-in failures. + auth->SignOut(); + auto login_future = auth->SignInAnonymously(); + Await(login_future, "Auth sign-in"); + auto* login_result = login_future.result(); + if (login_result) { + const firebase::auth::User user = login_result->user; + LogMessage("Signed in as %s user, uid: %s, email: %s.\n", + user.is_anonymous() ? "an anonymous" : "a non-anonymous", + user.uid().c_str(), user.email().c_str()); + } else { + LogMessage("ERROR: could not sign in"); + } + + // Note: Auth cannot be deleted while any of the futures issued by it are + // still valid. + login_future.Release(); + + LogMessage("Initialize Firebase Firestore."); + + // Use ModuleInitializer to initialize Database, ensuring no dependencies are + // missing. + firebase::firestore::Firestore* firestore = nullptr; + void* initialize_targets[] = {&firestore}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](firebase::App* app, void* data) { + LogMessage("Attempt to initialize Firebase Firestore."); + void** targets = reinterpret_cast(data); + firebase::InitResult result; + *reinterpret_cast(targets[0]) = + firebase::firestore::Firestore::GetInstance(app, &result); + return result; + }}; + + firebase::ModuleInitializer initializer; + initializer.Initialize(app, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + Await(initializer.InitializeLastResult(), "Initialize"); + + if (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Firebase libraries: %s", + initializer.InitializeLastResult().error_message()); + return -1; + } + LogMessage("Successfully initialized Firebase Firestore."); + + firestore->set_log_level(firebase::kLogLevelDebug); + + if (firestore->app() != app) { + LogMessage("ERROR: failed to get App the Firestore was created with."); + } + + firebase::firestore::Settings settings = firestore->settings(); + firestore->set_settings(settings); + LogMessage("Successfully set Firestore settings."); + + LogMessage("Testing non-wrapping types."); + const firebase::Timestamp timestamp{1, 2}; + if (timestamp.seconds() != 1 || timestamp.nanoseconds() != 2) { + LogMessage("ERROR: Timestamp creation failed."); + } + const firebase::firestore::SnapshotMetadata metadata{ + /*has_pending_writes*/ false, /*is_from_cache*/ true}; + if (metadata.has_pending_writes() || !metadata.is_from_cache()) { + LogMessage("ERROR: SnapshotMetadata creation failed."); + } + const firebase::firestore::GeoPoint point{1.23, 4.56}; + if (point.latitude() != 1.23 || point.longitude() != 4.56) { + LogMessage("ERROR: GeoPoint creation failed."); + } + LogMessage("Tested non-wrapping types."); + + LogMessage("Testing collections."); + firebase::firestore::CollectionReference collection = + firestore->Collection("foo"); + if (collection.id() != "foo") { + LogMessage("ERROR: failed to get collection id."); + } + if (collection.Document("bar").path() != "foo/bar") { + LogMessage("ERROR: failed to get path of a nested document."); + } + LogMessage("Tested collections."); + + LogMessage("Testing documents."); + firebase::firestore::DocumentReference document = + firestore->Document("foo/bar"); + if (document.firestore() != firestore) { + LogMessage("ERROR: failed to get Firestore from document."); + } + + if (document.path() != "foo/bar") { + LogMessage("ERROR: failed to get path string from document."); + } + + LogMessage("Testing Set()."); + Await(document.Set(firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}, + {"int", firebase::firestore::FieldValue::Integer(123)}}), + "document.Set"); + + LogMessage("Testing Update()."); + Await(document.Update(firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(321)}}), + "document.Update"); + + LogMessage("Testing Get()."); + auto doc_future = document.Get(); + if (Await(doc_future, "document.Get")) { + const firebase::firestore::DocumentSnapshot* snapshot = doc_future.result(); + if (snapshot == nullptr) { + LogMessage("ERROR: failed to read document."); + } else { + for (const auto& kv : snapshot->GetData()) { + if (kv.second.type() == + firebase::firestore::FieldValue::Type::kString) { + LogMessage("key is %s, value is %s", kv.first.c_str(), + kv.second.string_value().c_str()); + } else if (kv.second.type() == + firebase::firestore::FieldValue::Type::kInteger) { + LogMessage("key is %s, value is %ld", kv.first.c_str(), + kv.second.integer_value()); + } else { + // Log unexpected type for debugging. + LogMessage("key is %s, value is neither string nor integer", + kv.first.c_str()); + } + } + } + } + + LogMessage("Testing Delete()."); + Await(document.Delete(), "document.Delete"); + LogMessage("Tested document operations."); + + TestEventListener + document_event_listener{"for document"}; + firebase::firestore::ListenerRegistration registration = + document_event_listener.AttachTo(&document); + Await(document_event_listener, "document.AddSnapshotListener"); + registration.Remove(); + LogMessage("Successfully added and removed document snapshot listener."); + + LogMessage("Testing batch write."); + firebase::firestore::WriteBatch batch = firestore->batch(); + batch.Set(collection.Document("one"), + firebase::firestore::MapFieldValue{ + {"str", firebase::firestore::FieldValue::String("foo")}}); + batch.Set(collection.Document("two"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(123)}}); + Await(batch.Commit(), "batch.Commit"); + LogMessage("Tested batch write."); + + LogMessage("Testing transaction."); + Await(firestore->RunTransaction( + [collection](firebase::firestore::Transaction& transaction, + std::string&) -> firebase::firestore::Error { + transaction.Update( + collection.Document("one"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(123)}}); + transaction.Delete(collection.Document("two")); + transaction.Set( + collection.Document("three"), + firebase::firestore::MapFieldValue{ + {"int", firebase::firestore::FieldValue::Integer(321)}}); + return firebase::firestore::kErrorOk; + }), + "firestore.RunTransaction"); + LogMessage("Tested transaction."); + + LogMessage("Testing query."); + firebase::firestore::Query query = + collection + .WhereGreaterThan("int", + firebase::firestore::FieldValue::Boolean(true)) + .Limit(3); + auto query_future = query.Get(); + if (Await(query_future, "query.Get")) { + const firebase::firestore::QuerySnapshot* snapshot = query_future.result(); + if (snapshot == nullptr) { + LogMessage("ERROR: failed to fetch query result."); + } else { + for (const auto& doc : snapshot->documents()) { + if (doc.id() == "one" || doc.id() == "three") { + LogMessage("doc %s is %ld", doc.id().c_str(), + doc.Get("int").integer_value()); + } else { + LogMessage("ERROR: unexpected document %s.", doc.id().c_str()); + } + } + } + } else { + LogMessage("ERROR: failed to fetch query result."); + } + LogMessage("Tested query."); + + LogMessage("Shutdown the Firestore library."); + delete firestore; + firestore = nullptr; + + LogMessage("Shutdown Auth."); + delete auth; + LogMessage("Shutdown Firebase App."); + delete app; + + // Log this as the last line to ensure all test cases above goes through. + // The test harness will check this line appears. + LogMessage("Tests PASS."); + + // Wait until the user wants to quit the app. + while (!ProcessEvents(1000)) { + } + + return 0; +} diff --git a/admob/testapp/src/desktop/desktop_main.cc b/firestore/testapp/src/desktop/desktop_main.cc similarity index 52% rename from admob/testapp/src/desktop/desktop_main.cc rename to firestore/testapp/src/desktop/desktop_main.cc index 99ace543..3a027f82 100644 --- a/admob/testapp/src/desktop/desktop_main.cc +++ b/firestore/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,8 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { return std::string(); } + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +82,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory(FIREBASE_CONFIG_STRING[0] != '\0' + ? FIREBASE_CONFIG_STRING + : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +105,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10; +} +#endif diff --git a/invites/testapp/src/ios/ios_main.mm b/firestore/testapp/src/ios/ios_main.mm old mode 100644 new mode 100755 similarity index 87% rename from invites/testapp/src/ios/ios_main.mm rename to firestore/testapp/src/ios/ios_main.mm index 6ccb2de5..567f7b0e --- a/invites/testapp/src/ios/ios_main.mm +++ b/firestore/testapp/src/ios/ios_main.mm @@ -18,9 +18,9 @@ #include "main.h" -extern "C" int common_main(int argc, const char* argv[]); +extern "C" int common_main(int argc, const char *argv[]); -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property(nonatomic, strong) UIWindow *window; @@ -58,12 +58,10 @@ bool ProcessEvents(int msec) { return g_shutdown; } -WindowContext GetWindowContext() { - return g_parent_view; -} +WindowContext GetWindowContext() { return g_parent_view; } // Log a message that can be viewed in the console. -void LogMessage(const char* format, ...) { +void LogMessage(const char *format, ...) { va_list args; NSString *formatString = @(format); @@ -79,7 +77,7 @@ void LogMessage(const char* format, ...) { }); } -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { @autoreleasepool { UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } @@ -88,8 +86,8 @@ int main(int argc, char* argv[]) { @implementation AppDelegate -- (BOOL)application:(UIApplication*)application - didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { g_shutdown_complete = [[NSCondition alloc] init]; g_shutdown_signal = [[NSCondition alloc] init]; [g_shutdown_complete lock]; @@ -101,6 +99,7 @@ - (BOOL)application:(UIApplication*)application g_text_view = [[UITextView alloc] initWithFrame:viewController.view.bounds]; + g_text_view.accessibilityIdentifier = @"Logger"; g_text_view.editable = NO; g_text_view.scrollEnabled = YES; g_text_view.userInteractionEnabled = YES; diff --git a/admob/testapp/src/main.h b/firestore/testapp/src/main.h similarity index 100% rename from admob/testapp/src/main.h rename to firestore/testapp/src/main.h diff --git a/firestore/testapp/testapp.xcodeproj/project.pbxproj b/firestore/testapp/testapp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ed7634b5 --- /dev/null +++ b/firestore/testapp/testapp.xcodeproj/project.pbxproj @@ -0,0 +1,398 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 07D2E7EAE71115C7E5A3CD39 /* libPods-testapp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CFB4B133F33186AB751527C6 /* libPods-testapp.a */; }; + 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 */; }; + 529227211C85FB6A00C89379 /* common_main.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5292271F1C85FB6A00C89379 /* common_main.cc */; }; + 529227241C85FB7600C89379 /* ios_main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 529227221C85FB7600C89379 /* ios_main.mm */; }; + 52B71EBB1C8600B600398745 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 52B71EBA1C8600B600398745 /* Images.xcassets */; }; + B64AAF0C22EBB8570019A5BD /* firebase_auth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64AAF0922EBB8560019A5BD /* firebase_auth.framework */; }; + B64AAF0E22EBB8570019A5BD /* firebase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64AAF0B22EBB8560019A5BD /* firebase.framework */; }; + B64AAF1022EBBAC30019A5BD /* firebase_firestore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B64AAF0F22EBBAC20019A5BD /* firebase_firestore.framework */; }; + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 529226D21C85F68000C89379 /* testapp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = testapp.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; }; + 5292271F1C85FB6A00C89379 /* common_main.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = common_main.cc; path = src/common_main.cc; sourceTree = ""; }; + 529227201C85FB6A00C89379 /* main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = main.h; path = src/main.h; sourceTree = ""; }; + 529227221C85FB7600C89379 /* ios_main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_main.mm; path = src/ios/ios_main.mm; sourceTree = ""; }; + 52B71EBA1C8600B600398745 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = testapp/Images.xcassets; sourceTree = ""; }; + 52FD1FF81C85FFA000BC68E3 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = testapp/Info.plist; sourceTree = ""; }; + 561521B5AB75C63495CBCDE9 /* Pods-testapp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testapp.release.xcconfig"; path = "Target Support Files/Pods-testapp/Pods-testapp.release.xcconfig"; sourceTree = ""; }; + 7B961A65741D8D4C337CD503 /* Pods-testapp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-testapp.debug.xcconfig"; path = "Target Support Files/Pods-testapp/Pods-testapp.debug.xcconfig"; sourceTree = ""; }; + B64AAF0922EBB8560019A5BD /* firebase_auth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = firebase_auth.framework; sourceTree = ""; }; + B64AAF0A22EBB8560019A5BD /* firebase_database.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = firebase_database.framework; sourceTree = ""; }; + B64AAF0B22EBB8560019A5BD /* firebase.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = firebase.framework; sourceTree = ""; }; + B64AAF0F22EBBAC20019A5BD /* firebase_firestore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = firebase_firestore.framework; sourceTree = ""; }; + CFB4B133F33186AB751527C6 /* libPods-testapp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-testapp.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 529226CF1C85F68000C89379 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B64AAF1022EBBAC30019A5BD /* firebase_firestore.framework in Frameworks */, + B64AAF0C22EBB8570019A5BD /* firebase_auth.framework in Frameworks */, + B64AAF0E22EBB8570019A5BD /* firebase.framework in Frameworks */, + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + 07D2E7EAE71115C7E5A3CD39 /* libPods-testapp.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 52B71EBA1C8600B600398745 /* Images.xcassets */, + 52FD1FF81C85FFA000BC68E3 /* Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + 5C0AA7C41D61214BE0B5CB4C /* Pods */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* testapp.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B64AAF0F22EBBAC20019A5BD /* firebase_firestore.framework */, + B64AAF0922EBB8560019A5BD /* firebase_auth.framework */, + B64AAF0A22EBB8560019A5BD /* firebase_database.framework */, + B64AAF0B22EBB8560019A5BD /* firebase.framework */, + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + CFB4B133F33186AB751527C6 /* libPods-testapp.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 5292271D1C85FB5500C89379 /* src */ = { + isa = PBXGroup; + children = ( + 5292271F1C85FB6A00C89379 /* common_main.cc */, + 529227201C85FB6A00C89379 /* main.h */, + 5292271E1C85FB5B00C89379 /* ios */, + ); + name = src; + sourceTree = ""; + }; + 5292271E1C85FB5B00C89379 /* ios */ = { + isa = PBXGroup; + children = ( + 529227221C85FB7600C89379 /* ios_main.mm */, + ); + name = ios; + sourceTree = ""; + }; + 5C0AA7C41D61214BE0B5CB4C /* Pods */ = { + isa = PBXGroup; + children = ( + 7B961A65741D8D4C337CD503 /* Pods-testapp.debug.xcconfig */, + 561521B5AB75C63495CBCDE9 /* Pods-testapp.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* testapp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "testapp" */; + buildPhases = ( + C504BBC3599D8DE4D5CBFBFC /* [CP] Check Pods Manifest.lock */, + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* Resources */, + 8B4C0569EC39C8FCDFBCA4EB /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = testapp; + productName = testapp; + productReference = 529226D21C85F68000C89379 /* testapp.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; + }; + }; + }; + buildConfigurationList = 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "testapp" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + English, + en, + ); + mainGroup = 529226C91C85F68000C89379; + productRefGroup = 529226D31C85F68000C89379 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 529226D11C85F68000C89379 /* testapp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 529226D01C85F68000C89379 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D66B16871CE46E8900E5638A /* LaunchScreen.storyboard in Resources */, + 52B71EBB1C8600B600398745 /* Images.xcassets in Resources */, + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 8B4C0569EC39C8FCDFBCA4EB /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-testapp/Pods-testapp-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/gRPC-C++/gRPCCertificates-Cpp.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/gRPCCertificates-Cpp.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-testapp/Pods-testapp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + C504BBC3599D8DE4D5CBFBFC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-testapp-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 529226CE1C85F68000C89379 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 529227241C85FB7600C89379 /* ios_main.mm in Sources */, + 529227211C85FB6A00C89379 /* common_main.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; + baseConfigurationReference = 7B961A65741D8D4C337CD503 /* Pods-testapp.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + ); + INFOPLIST_FILE = testapp/Info.plist; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 529226FB1C85F68000C89379 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 561521B5AB75C63495CBCDE9 /* Pods-testapp.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "\"$(SRCROOT)/src\"", + ); + INFOPLIST_FILE = testapp/Info.plist; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 529226CD1C85F68000C89379 /* Build configuration list for PBXProject "testapp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226F71C85F68000C89379 /* Debug */, + 529226F81C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "testapp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 529226FA1C85F68000C89379 /* Debug */, + 529226FB1C85F68000C89379 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 529226CA1C85F68000C89379 /* Project object */; +} diff --git a/firestore/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/firestore/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/firestore/testapp/testapp/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/invites/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/firestore/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json similarity index 100% rename from invites/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json rename to firestore/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json diff --git a/firestore/testapp/testapp/Info.plist b/firestore/testapp/testapp/Info.plist new file mode 100644 index 00000000..341ff801 --- /dev/null +++ b/firestore/testapp/testapp/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.firestore.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + YOUR_REVERSED_CLIENT_ID + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/admob/testapp/AndroidManifest.xml b/functions/testapp/AndroidManifest.xml similarity index 76% rename from admob/testapp/AndroidManifest.xml rename to functions/testapp/AndroidManifest.xml index 13530578..0b38246d 100644 --- a/admob/testapp/AndroidManifest.xml +++ b/functions/testapp/AndroidManifest.xml @@ -1,16 +1,17 @@ - + + android:exported = "true" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize"> diff --git a/functions/testapp/CMakeLists.txt b/functions/testapp/CMakeLists.txt new file mode 100644 index 00000000..c6eb6903 --- /dev/null +++ b/functions/testapp/CMakeLists.txt @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase samples. +# 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() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +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() + +# 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) + +# Sample source files. +set(FIREBASE_SAMPLE_COMMON_SRCS + src/main.h + src/common_main.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Sample uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_SAMPLE_ANDROID_SRCS + src/android/android_main.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") + + # Define the target as a shared library, as that is what gradle expects. + set(target_name "android_main") + add_library(${target_name} SHARED + ${FIREBASE_SAMPLE_ANDROID_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + target_link_libraries(${target_name} + log android atomic native_app_glue + ) + + target_include_directories(${target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS) +else() + # Build a desktop application. + + # 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 sample. + set(FIREBASE_SAMPLE_DESKTOP_SRCS + src/desktop/desktop_main.cc + ) + + set(target_name "desktop_testapp") + add_executable(${target_name} + ${FIREBASE_SAMPLE_DESKTOP_SRCS} + ${FIREBASE_SAMPLE_COMMON_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 rpcrt4 ole32) + 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 ${config}) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${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_functions firebase_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/admob/testapp/LICENSE b/functions/testapp/LICENSE similarity index 100% rename from admob/testapp/LICENSE rename to functions/testapp/LICENSE diff --git a/invites/testapp/LaunchScreen.storyboard b/functions/testapp/LaunchScreen.storyboard similarity index 100% rename from invites/testapp/LaunchScreen.storyboard rename to functions/testapp/LaunchScreen.storyboard diff --git a/functions/testapp/Podfile b/functions/testapp/Podfile new file mode 100644 index 00000000..7197d817 --- /dev/null +++ b/functions/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# Cloud Functions for Firebase test application. +target 'testapp' do + pod 'Firebase/Functions', '10.25.0' + pod 'Firebase/Auth', '10.25.0' +end diff --git a/functions/testapp/build.gradle b/functions/testapp/build.gradle new file mode 100644 index 00000000..d47c3488 --- /dev/null +++ b/functions/testapp/build.gradle @@ -0,0 +1,77 @@ +// 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' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' + } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' + + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] + } + } + + defaultConfig { + applicationId 'com.google.firebase.cpp.functions.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + functions +} + +apply plugin: 'com.google.gms.google-services' diff --git a/functions/testapp/functions/index.js b/functions/testapp/functions/index.js new file mode 100644 index 00000000..a8d1905d --- /dev/null +++ b/functions/testapp/functions/index.js @@ -0,0 +1,43 @@ +/** + * Copyright 2018 Google Inc. 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. + */ +'use strict'; + +const functions = require('firebase-functions/v1'); +const admin = require('firebase-admin'); +admin.initializeApp(); + +// Adds two numbers to each other. +exports.addNumbers = functions.https.onCall((data) => { + // Numbers passed from the client. + const firstNumber = data.firstNumber; + const secondNumber = data.secondNumber; + + // Checking that attributes are present and are numbers. + if (!Number.isFinite(firstNumber) || !Number.isFinite(secondNumber)) { + // Throwing an HttpsError so that the client gets the error details. + throw new functions.https.HttpsError('invalid-argument', 'The function ' + + 'must be called with two arguments "firstNumber" and "secondNumber" ' + + 'which must both be numbers.'); + } + + // returning result. + return { + firstNumber: firstNumber, + secondNumber: secondNumber, + operator: '+', + operationResult: firstNumber + secondNumber, + }; +}); diff --git a/functions/testapp/functions/package.json b/functions/testapp/functions/package.json new file mode 100644 index 00000000..86b46886 --- /dev/null +++ b/functions/testapp/functions/package.json @@ -0,0 +1,9 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase C++ Quickstart", + "dependencies": { + "firebase-admin": "~5.10.0", + "firebase-functions": "0.9.0" + }, + "private": true +} diff --git a/functions/testapp/gradle.properties b/functions/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/functions/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/invites/testapp/gradle/wrapper/gradle-wrapper.jar b/functions/testapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from invites/testapp/gradle/wrapper/gradle-wrapper.jar rename to functions/testapp/gradle/wrapper/gradle-wrapper.jar diff --git a/admob/testapp/gradle/wrapper/gradle-wrapper.properties b/functions/testapp/gradle/wrapper/gradle-wrapper.properties similarity index 52% rename from admob/testapp/gradle/wrapper/gradle-wrapper.properties rename to functions/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/admob/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/functions/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/invites/testapp/gradlew b/functions/testapp/gradlew similarity index 100% rename from invites/testapp/gradlew rename to functions/testapp/gradlew diff --git a/invites/testapp/gradlew.bat b/functions/testapp/gradlew.bat similarity index 100% rename from invites/testapp/gradlew.bat rename to functions/testapp/gradlew.bat diff --git a/functions/testapp/proguard.pro b/functions/testapp/proguard.pro new file mode 100644 index 00000000..2d04b8a9 --- /dev/null +++ b/functions/testapp/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/functions/testapp/readme.md b/functions/testapp/readme.md new file mode 100644 index 00000000..8d1537a6 --- /dev/null +++ b/functions/testapp/readme.md @@ -0,0 +1,214 @@ +Cloud Functions for Firebase Quickstart +======================== + +The Cloud Functions for Firebase Test Application (testapp) demonstrates +Cloud Function operations with the Firebase C++ SDK for Cloud Functions. +The application has no user interface and simply logs actions it's performing +to the console. + +The testapp performs the following: + - Creates a firebase::App in a platform-specific way. The App holds + platform-specific context that's used by other Firebase APIs, and is a + central point for communication between the Cloud Function C++ and + Firebase Auth C++ libraries. + - Calls various integration test Cloud Functions and verifies their results. + - Shuts down the Cloud Functions, Firebase Auth, and Firebase App systems. + +Introduction +------------ + +- [Read more about Cloud Functions for Firebase](http://go/firebase-functions-api) + +Building and Running the testapp +-------------------------------- + +### Deploying functions. +- Install the [Firebase CLI](https://firebase.google.com/docs/cli/). +- Deploy the provided functions. + ```bash + # Move to the `functions` subdirectory of quickstart-android + cd functions + + # Install all of the dependencies of the cloud functions + cd functions + npm install + cd ../ + + # Deploy functions to your Firebase project + firebase --project=YOUR_PROJECT_ID deploy --only functions + ``` + +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + ``` + sudo gem install cocoapods --pre + ``` + - From the testapp directory, install the CocoaPods listed in the Podfile + by running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + ``` + open testapp.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + - Register your iOS app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.functions.testapp" as the iOS Bundle + ID while you're testing. You can omit App Store ID while testing. + - Add the GoogleService-Info.plist that you downloaded from Firebase + console to the testapp root directory. This file identifies your iOS app + to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the testapp to use anonymous sign-in to + authenticate with Cloud Functions, which requires a signed-in user by + default (an anonymous user will suffice). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase_auth.framework + - frameworks/ios/universal/firebase_functions.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - e.g. If you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in XCode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - In XCode, build & run the sample on an iOS device or simulator. + - The testapp has no interative interface. The output of the app can be viewed + via the console or on the device's display. In Xcode, select + "View --> Debug Area --> Activate Console" from the menu to view the console. + +### Android + - Register your Android app with Firebase. + - Create a new app on + the [Firebase console](https://firebase.google.com/console/), and attach + your Android app to it. + - You can use "com.google.firebase.cpp.functions.testapp" as the Package + Name while you're testing. + - To + [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + or this command on Windows, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + - Add the `google-services.json` file that you downloaded from Firebase + console to the root directory of testapp. This file identifies your + Android app to the Firebase backend. + - In the Firebase console for your app, select "Auth", then enable + "Anonymous". This will allow the testapp to use anonymous sign-in to + authenticate with Cloud Functions. + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. + For example, in the project directory: + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + - Ensure the Android SDK and NDK locations are set in Android Studio. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + - Open *build.gradle* in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + - Install the SDK Platforms that Android Studio reports missing. + - Build the testapp and run it on an Android device or emulator. + - The testapp has no interactive interface. The output of the app can be + viewed on the device's display, or in the logcat output of Android studio or + by running "adb logcat *:W android_main firebase" from the command line. + +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. Note that Functions uses a stubbed implementation on desktop, + so functionality is not expected. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2016 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/functions/testapp/res/layout/main.xml b/functions/testapp/res/layout/main.xml new file mode 100644 index 00000000..d3ffb630 --- /dev/null +++ b/functions/testapp/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/functions/testapp/res/values/strings.xml b/functions/testapp/res/values/strings.xml new file mode 100644 index 00000000..2cf3eca3 --- /dev/null +++ b/functions/testapp/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Cloud Functions for Firebase Test + diff --git a/functions/testapp/settings.gradle b/functions/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/functions/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/invites/testapp/src/android/android_main.cc b/functions/testapp/src/android/android_main.cc similarity index 98% rename from invites/testapp/src/android/android_main.cc rename to functions/testapp/src/android/android_main.cc index 73cb30e7..4b86ce30 100644 --- a/invites/testapp/src/android/android_main.cc +++ b/functions/testapp/src/android/android_main.cc @@ -50,6 +50,12 @@ bool ProcessEvents(int msec) { return g_destroy_requested | g_restarted; } +std::string PathForResource() { + ANativeActivity* nativeActivity = g_app_state->activity; + std::string result(nativeActivity->internalDataPath); + return result + "/"; +} + // Get the activity. jobject GetActivity() { return g_app_state->activity->clazz; } diff --git a/functions/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/functions/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java new file mode 100644 index 00000000..351cf3b4 --- /dev/null +++ b/functions/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -0,0 +1,57 @@ +// Copyright 2016 Google Inc. 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. + +package com.google.firebase.example; + +import android.app.Activity; +import android.os.Handler; +import android.os.Looper; +import android.view.Window; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +/** + * A utility class, encapsulating the data and methods required to log arbitrary text to the screen, + * via a non-editable TextView. + */ +public class LoggingUtils { + public static TextView sTextView = null; + + public static void initLogWindow(Activity activity) { + LinearLayout linearLayout = new LinearLayout(activity); + ScrollView scrollView = new ScrollView(activity); + TextView textView = new TextView(activity); + textView.setTag("Logger"); + linearLayout.addView(scrollView); + scrollView.addView(textView); + Window window = activity.getWindow(); + window.takeSurface(null); + window.setContentView(linearLayout); + sTextView = textView; + } + + public static void addLogText(final String text) { + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + if (sTextView != null) { + sTextView.append(text); + } + } + }); + } +} diff --git a/functions/testapp/src/common_main.cc b/functions/testapp/src/common_main.cc new file mode 100644 index 00000000..5e5e0019 --- /dev/null +++ b/functions/testapp/src/common_main.cc @@ -0,0 +1,173 @@ +// Copyright 2016 Google Inc. 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 "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/functions.h" +#include "firebase/future.h" +#include "firebase/log.h" +#include "firebase/util.h" + +// Thin OS abstraction layer. +#include "main.h" // NOLINT + +// Wait for a Future to be completed. If the Future returns an error, it will +// be logged. +void WaitForCompletion(const firebase::FutureBase& future, const char* name) { + while (future.status() == firebase::kFutureStatusPending) { + ProcessEvents(100); + } +} + + +extern "C" int common_main(int argc, const char* argv[]) { + ::firebase::App* app; + +#if defined(__ANDROID__) + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); +#else + app = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogMessage("Initialized Firebase App."); + + LogMessage("Initializing Firebase Auth and Cloud Functions."); + + // Use ModuleInitializer to initialize both Auth and Functions, ensuring no + // dependencies are missing. + ::firebase::functions::Functions* functions = nullptr; + ::firebase::auth::Auth* auth = nullptr; + void* initialize_targets[] = {&auth, &functions}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + LogMessage("Attempt to initialize Firebase Auth."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + LogMessage("Attempt to initialize Cloud Functions."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::functions::Functions**>(targets[1]) = + ::firebase::functions::Functions::GetInstance(app, &result); + return result; + }}; + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + if (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Firebase libraries: %s", + initializer.InitializeLastResult().error_message()); + ProcessEvents(2000); + return 1; + } + LogMessage("Successfully initialized Firebase Auth and Cloud Functions."); + + // To test against a local emulator, uncomment this line: + // functions->UseFunctionsEmulator("http://localhost:5005"); + // Or when running in an Android emulator: + // functions->UseFunctionsEmulator("http://10.0.2.2:5005"); + + // Optionally, sign in using Auth before accessing Functions. + { + firebase::Future sign_in_future = + auth->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { + LogMessage("Auth: Signed in anonymously."); + } else { + LogMessage("ERROR: Could not sign in anonymously. Error %d: %s", + sign_in_future.error(), sign_in_future.error_message()); + LogMessage( + " Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."); + LogMessage( + " Attempting to connect to Cloud Functions anyway. This may fail " + "depending on the function."); + } + } + + + // Create a callable. + LogMessage("Calling addNumbers"); + firebase::functions::HttpsCallableReference addNumbers; + addNumbers = functions->GetHttpsCallable("addNumbers"); + + firebase::Future future; + { + std::map data; + data["firstNumber"] = firebase::Variant(5); + data["secondNumber"] = firebase::Variant(7); + future = addNumbers.Call(firebase::Variant(data)); + } + WaitForCompletion(future, "Call"); + if (future.error() != firebase::functions::kErrorNone) { + LogMessage("FAILED!"); + LogMessage(" Error %d: %s", future.error(), future.error_message()); + } else { + firebase::Variant result = future.result()->data(); + int op_result = + static_cast(result.map()["operationResult"].int64_value()); + const int expected = 12; + if (op_result != expected) { + LogMessage("FAILED!"); + LogMessage(" Expected: %d, Actual: %d", expected, op_result); + } else { + LogMessage("SUCCESS."); + LogMessage(" Got expected result: %d", op_result); + } + } + + LogMessage("Shutting down the Functions library."); + delete functions; + functions = nullptr; + + // Ensure that the ref we had is now invalid. + if (!addNumbers.is_valid()) { + LogMessage("SUCCESS: Reference was invalidated on library shutdown."); + } else { + LogMessage("ERROR: Reference is still valid after library shutdown."); + } + + LogMessage("Signing out from anonymous account."); + auth->SignOut(); + LogMessage("Shutting down the Auth library."); + delete auth; + auth = nullptr; + + LogMessage("Shutting down Firebase App."); + delete app; + + // Wait until the user wants to quit the app. + while (!ProcessEvents(1000)) { + } + + return 0; +} diff --git a/invites/testapp/src/desktop/desktop_main.cc b/functions/testapp/src/desktop/desktop_main.cc similarity index 52% rename from invites/testapp/src/desktop/desktop_main.cc rename to functions/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/invites/testapp/src/desktop/desktop_main.cc +++ b/functions/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/functions/testapp/src/ios/ios_main.mm b/functions/testapp/src/ios/ios_main.mm new file mode 100755 index 00000000..ee0c792c --- /dev/null +++ b/functions/testapp/src/ios/ios_main.mm @@ -0,0 +1,127 @@ +// Copyright 2016 Google Inc. 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. + +#import + +#include + +#include "main.h" + +extern "C" int common_main(int argc, const char *argv[]); + +@interface AppDelegate : UIResponder + +@property(nonatomic, strong) UIWindow *window; + +@end + +@interface FTAViewController : UIViewController + +@end + +static int g_exit_status = 0; +static bool g_shutdown = false; +static NSCondition *g_shutdown_complete; // NOLINT +static NSCondition *g_shutdown_signal; // NOLINT +static UITextView *g_text_view; // NOLINT +static UIView *g_parent_view; // NOLINT + +@implementation FTAViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + g_parent_view = self.view; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + const char *argv[] = {FIREBASE_TESTAPP_NAME}; + [g_shutdown_signal lock]; + g_exit_status = common_main(1, argv); + [g_shutdown_complete signal]; + }); +} + +@end + +bool ProcessEvents(int msec) { + [g_shutdown_signal + waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:static_cast(msec) / 1000.0f]]; + return g_shutdown; +} + +std::string PathForResource() { + NSArray *paths = + NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = paths.firstObject; + // Force a trailing slash by removing any that exists, then appending another. + return std::string( + [[documentsDirectory stringByStandardizingPath] stringByAppendingString:@"/"].UTF8String); +} + +WindowContext GetWindowContext() { return g_parent_view; } + +// Log a message that can be viewed in the console. +void LogMessage(const char *format, ...) { + va_list args; + NSString *formatString = @(format); + + va_start(args, format); + NSString *message = [[NSString alloc] initWithFormat:formatString arguments:args]; + va_end(args); + + NSLog(@"%@", message); + message = [message stringByAppendingString:@"\n"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + g_text_view.text = [g_text_view.text stringByAppendingString:message]; + }); +} + +int main(int argc, char *argv[]) { + @autoreleasepool { + UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } + return g_exit_status; +} + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + g_shutdown_complete = [[NSCondition alloc] init]; + g_shutdown_signal = [[NSCondition alloc] init]; + [g_shutdown_complete lock]; + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + FTAViewController *viewController = [[FTAViewController alloc] init]; + self.window.rootViewController = viewController; + [self.window makeKeyAndVisible]; + + g_text_view = [[UITextView alloc] initWithFrame:viewController.view.bounds]; + + g_text_view.accessibilityIdentifier = @"Logger"; + g_text_view.editable = NO; + g_text_view.scrollEnabled = YES; + g_text_view.userInteractionEnabled = YES; + + [viewController.view addSubview:g_text_view]; + + return YES; +} + +- (void)applicationWillTerminate:(UIApplication *)application { + g_shutdown = true; + [g_shutdown_signal signal]; + [g_shutdown_complete wait]; +} + +@end diff --git a/invites/testapp/src/main.h b/functions/testapp/src/main.h similarity index 95% rename from invites/testapp/src/main.h rename to functions/testapp/src/main.h index 2eda2c10..f5980902 100644 --- a/invites/testapp/src/main.h +++ b/functions/testapp/src/main.h @@ -15,6 +15,7 @@ #ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT #define FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#include #if defined(__ANDROID__) #include #include @@ -38,6 +39,9 @@ extern "C" void LogMessage(const char* format, ...); // Returns true when an event requesting program-exit is received. bool ProcessEvents(int msec); +// Returns a path to a file suitable for the given platform. +std::string PathForResource(); + // WindowContext represents the handle to the parent window. It's type // (and usage) vary based on the OS. #if defined(__ANDROID__) diff --git a/invites/testapp/testapp.xcodeproj/project.pbxproj b/functions/testapp/testapp.xcodeproj/project.pbxproj similarity index 99% rename from invites/testapp/testapp.xcodeproj/project.pbxproj rename to functions/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/invites/testapp/testapp.xcodeproj/project.pbxproj +++ b/functions/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/invites/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/functions/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from invites/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json rename to functions/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json diff --git a/functions/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/functions/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 00000000..6f870a46 --- /dev/null +++ b/functions/testapp/testapp/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/functions/testapp/testapp/Info.plist b/functions/testapp/testapp/Info.plist new file mode 100644 index 00000000..1b49a974 --- /dev/null +++ b/functions/testapp/testapp/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.functions.testapp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + google + CFBundleURLSchemes + + YOUR_REVERSED_CLIENT_ID + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + + diff --git a/gma/testapp/AndroidManifest.xml b/gma/testapp/AndroidManifest.xml new file mode 100644 index 00000000..f443dd14 --- /dev/null +++ b/gma/testapp/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/gma/testapp/CMakeLists.txt b/gma/testapp/CMakeLists.txt new file mode 100644 index 00000000..2132660c --- /dev/null +++ b/gma/testapp/CMakeLists.txt @@ -0,0 +1,111 @@ +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase samples. +# 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() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +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() + +# Sample source files. +set(FIREBASE_SAMPLE_COMMON_SRCS + src/main.h + src/common_main.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Sample uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_SAMPLE_ANDROID_SRCS + src/android/android_main.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") + + # Define the target as a shared library, as that is what gradle expects. + set(target_name "android_main") + add_library(${target_name} SHARED + ${FIREBASE_SAMPLE_ANDROID_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + target_link_libraries(${target_name} + log android atomic native_app_glue + ) + + target_include_directories(${target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS) +else() + # Build a desktop application. + + # 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 sample. + set(FIREBASE_SAMPLE_DESKTOP_SRCS + src/desktop/desktop_main.cc + ) + + set(target_name "desktop_testapp") + add_executable(${target_name} + ${FIREBASE_SAMPLE_DESKTOP_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS pthread) + elseif(MSVC) + set(ADDITIONAL_LIBS) + 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 ${config}) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${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) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/invites/testapp/LICENSE b/gma/testapp/LICENSE similarity index 99% rename from invites/testapp/LICENSE rename to gma/testapp/LICENSE index 25dd9993..d6456956 100644 --- a/invites/testapp/LICENSE +++ b/gma/testapp/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016 Google Inc. + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/gma/testapp/LaunchScreen.storyboard b/gma/testapp/LaunchScreen.storyboard new file mode 100644 index 00000000..673e0f7e --- /dev/null +++ b/gma/testapp/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/gma/testapp/Podfile b/gma/testapp/Podfile new file mode 100644 index 00000000..4683028b --- /dev/null +++ b/gma/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# GMA test application. +target 'testapp' do + pod 'Google-Mobile-Ads-SDK', '11.2.0' + pod 'Firebase/Analytics', '10.25.0' +end diff --git a/gma/testapp/build.gradle b/gma/testapp/build.gradle new file mode 100644 index 00000000..4cf159a9 --- /dev/null +++ b/gma/testapp/build.gradle @@ -0,0 +1,79 @@ +// 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' } + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } +} + +allprojects { + repositories { + mavenLocal() + maven { url 'https://maven.google.com' } + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' + + 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 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" + } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') + } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } +} + +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + gma +} + +apply plugin: 'com.google.gms.google-services' + +// com.google.gms.googleservices.GoogleServicesPlugin.config.disableVersionCheck = true diff --git a/gma/testapp/gradle.properties b/gma/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/gma/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/gma/testapp/gradle/wrapper/gradle-wrapper.jar b/gma/testapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/gma/testapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gma/testapp/gradle/wrapper/gradle-wrapper.properties b/gma/testapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..65340c1b --- /dev/null +++ b/gma/testapp/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 27 14:03:45 PST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/gma/testapp/gradlew b/gma/testapp/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/gma/testapp/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + 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/gma/testapp/gradlew.bat b/gma/testapp/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gma/testapp/gradlew.bat @@ -0,0 +1,90 @@ +@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/gma/testapp/proguard.pro b/gma/testapp/proguard.pro new file mode 100644 index 00000000..54cd248b --- /dev/null +++ b/gma/testapp/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/gma/testapp/readme.md b/gma/testapp/readme.md new file mode 100644 index 00000000..4143cb7e --- /dev/null +++ b/gma/testapp/readme.md @@ -0,0 +1,195 @@ +Firebase GMA Quickstart +============================== + +The Firebase Google Mobile Ads Test Application (testapp) demonstrates +loading and showing AdMob-served banners, interstitials and rewarded ads +using the Firebase GMA C++ SDK. The application has no user interface and +simply logs actions it's performing to the console while displaying the ads. + +Introduction +------------ + +- [Read more about Firebase GMA](https://firebase.google.com/docs/gma) + +Getting Started +--------------- + +### iOS + - Link your iOS app to the Firebase libraries. + - Get CocoaPods version 1 or later by running, + ``` + sudo gem install cocoapods --pre + ``` + - From the testapp directory, install the CocoaPods listed in the Podfile + by running, + ``` + pod install + ``` + - Open the generated Xcode workspace (which now has the CocoaPods), + ``` + open testapp.xcworkspace + ``` + - For further details please refer to the + [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). + - Register your iOS app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + and attach your iOS app to it. + - You can use "com.google.ios.admob.testapp" as the iOS Bundle ID + while you're testing. You can omit App Store ID while testing. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Add the following frameworks from the Firebase C++ SDK to the project: + - frameworks/ios/universal/firebase.framework + - frameworks/ios/universal/firebase_gma.framework + - You will need to either, + 1. Check "Copy items if needed" when adding the frameworks, or + 2. Add the framework path in "Framework Search Paths" + - For example, if you downloaded the Firebase C++ SDK to + `/Users/me/firebase_cpp_sdk`, + then you would add the path + `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. + - To add the path, in Xcode, select your project in the project + navigator, then select your target in the main window. + Select the "Build Settings" tab, and click "All" to see all + the build settings. Scroll down to "Search Paths", and add + your path to "Framework Search Paths". + - Update the AdMob App ID: + - In the `testapp/Info.plist`, update `GADApplicationIdentifier` with the + app ID for your iOS app, replacing 'YOUR_IOS_ADMOB_APP_ID'. + - For more information, see + [Update your Info.plist](https://developers.google.com/admob/ios/quick-start#manual_download) + - In Xcode, build & run the sample on an iOS device or simulator. + - The testapp displays a banner ad, an interstitial ad and a rewarded ad. You must + dismiss each ad to see the next. + - The output of the app can be viewed onscreen or via the console. To view + the console in Xcode, select "View --> Debug Area --> Activate Console" + from the menu. + +### Android + - Register your Android app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + and attach your Android app to it. + - You can use "com.google.android.admob.testapp" as the Package Name + while you're testing. + - To [generate a SHA1](https://developers.google.com/android/guides/client-auth) + run this command on Mac and Linux, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore + ``` + or this command on Windows, + ``` + keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore + ``` + - If keytool reports that you do not have a debug.keystore, you can + [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), + ``` + keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" + ``` + - Add the `google-services.json` file that you downloaded from Firebase + console to the root directory of testapp. This file identifies your + Android app to the Firebase backend. + - For further details please refer to the + [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the location of the Firebase C++ SDK by setting the + firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. + For example, in the project directory: + ``` + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + ``` + - Ensure the Android SDK and NDK locations are set in Android Studio. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. + - Open *build.gradle* in Android Studio. + - From the Android Studio launch menu, "Open an existing Android Studio + project", and select `build.gradle`. + - Install the SDK Platforms that Android Studio reports missing. + - Update the GMA App ID: + - In the `AndroidManifest.xml`, update + `com.google.android.gms.ads.APPLICATION_ID` with the same app ID, + replacing 'YOUR_ANDROID_ADMOB_APP_ID'. + - For more information, see + [Update your AndroidManifest.xml](https://developers.google.com/admob/android/quick-start#update_your_androidmanifestxml) + - Build the testapp and run it on an Android device or emulator. + - The testapp will initialize the GMA SDK, then load and display a test + banner ad, interstitial ad and rewarded ad. + - Tapping on an ad to verify the clickthrough process is possible, and the + test app will wait for each ad to be dismissed. + - While this is happening, information from the device log will be written + to an onscreen TextView. + - Logcat can also be used as normal. + +### Desktop + - Note: the testapp has no user interface, but the output can be viewed via + the console. The GMA SDK uses a stubbed implementation on desktop, so + functionality is not expected, and the app will end waiting for the interstitial + ad to be dismissed. + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways: + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + +Support +------- + +[https://firebase.google.com/support/](https://firebase.google.com/support/) + +License +------- + +Copyright 2022 Google, Inc. + +Licensed to the Apache Software Foundation (ASF) under one or more contributor +license agreements. See the NOTICE file distributed with this work for +additional information regarding copyright ownership. The ASF licenses this +file to you 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. diff --git a/admob/testapp/res/values/strings.xml b/gma/testapp/res/values/strings.xml similarity index 53% rename from admob/testapp/res/values/strings.xml rename to gma/testapp/res/values/strings.xml index 8589bd2c..a9a313a3 100644 --- a/admob/testapp/res/values/strings.xml +++ b/gma/testapp/res/values/strings.xml @@ -1,4 +1,4 @@ - Firebase AdMob Test + Firebase GMA Test diff --git a/gma/testapp/settings.gradle b/gma/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/gma/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/gma/testapp/src/android/android_main.cc b/gma/testapp/src/android/android_main.cc new file mode 100644 index 00000000..7a6de455 --- /dev/null +++ b/gma/testapp/src/android/android_main.cc @@ -0,0 +1,256 @@ +// Copyright 2016 Google Inc. 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 "main.h" // NOLINT + +// This implementation is derived from http://github.com/google/fplutil + +extern "C" int common_main(int argc, const char *argv[]); + +static struct android_app *g_app_state = nullptr; +static bool g_destroy_requested = false; +static bool g_started = false; +static bool g_restarted = false; +static pthread_mutex_t g_started_mutex; + +// Handle state changes from via native app glue. +static void OnAppCmd(struct android_app *app, int32_t cmd) { + g_destroy_requested |= cmd == APP_CMD_DESTROY; +} + +// Process events pending on the main thread. +// Returns true when the app receives an event requesting exit. +bool ProcessEvents(int msec) { + struct android_poll_source *source = nullptr; + int events; + int looperId = ALooper_pollAll(msec, nullptr, &events, + reinterpret_cast(&source)); + if (looperId >= 0 && source) { + source->process(g_app_state, source); + } + return g_destroy_requested | g_restarted; +} + +// Get the activity. +jobject GetActivity() { return g_app_state->activity->clazz; } + +// Get the window context. For Android, it's a jobject pointing to the Activity. +jobject GetWindowContext() { return g_app_state->activity->clazz; } + +// Find a class, attempting to load the class if it's not found. +jclass FindClass(JNIEnv *env, jobject activity_object, const char *class_name) { + jclass class_object = env->FindClass(class_name); + if (env->ExceptionCheck()) { + env->ExceptionClear(); + // If the class isn't found it's possible NativeActivity is being used by + // the application which means the class path is set to only load system + // classes. The following falls back to loading the class using the + // Activity before retrieving a reference to it. + jclass activity_class = env->FindClass("android/app/Activity"); + jmethodID activity_get_class_loader = env->GetMethodID( + activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); + + jobject class_loader_object = + env->CallObjectMethod(activity_object, activity_get_class_loader); + + jclass class_loader_class = env->FindClass("java/lang/ClassLoader"); + jmethodID class_loader_load_class = + env->GetMethodID(class_loader_class, "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + jstring class_name_object = env->NewStringUTF(class_name); + + class_object = static_cast(env->CallObjectMethod( + class_loader_object, class_loader_load_class, class_name_object)); + + if (env->ExceptionCheck()) { + env->ExceptionClear(); + class_object = nullptr; + } + env->DeleteLocalRef(class_name_object); + env->DeleteLocalRef(class_loader_object); + } + return class_object; +} + +// Vars that we need available for appending text to the log window: +class LoggingUtilsData { +public: + LoggingUtilsData() + : logging_utils_class_(nullptr), logging_utils_add_log_text_(0), + logging_utils_init_log_window_(0) {} + + ~LoggingUtilsData() { + JNIEnv *env = GetJniEnv(); + assert(env); + if (logging_utils_class_) { + env->DeleteGlobalRef(logging_utils_class_); + } + } + + void Init() { + JNIEnv *env = GetJniEnv(); + assert(env); + + jclass logging_utils_class = FindClass( + env, GetActivity(), "com/google/firebase/example/LoggingUtils"); + assert(logging_utils_class != 0); + + // Need to store as global references so it don't get moved during garbage + // collection. + logging_utils_class_ = + static_cast(env->NewGlobalRef(logging_utils_class)); + env->DeleteLocalRef(logging_utils_class); + + logging_utils_init_log_window_ = env->GetStaticMethodID( + logging_utils_class_, "initLogWindow", "(Landroid/app/Activity;)V"); + logging_utils_add_log_text_ = env->GetStaticMethodID( + logging_utils_class_, "addLogText", "(Ljava/lang/String;)V"); + + env->CallStaticVoidMethod(logging_utils_class_, + logging_utils_init_log_window_, GetActivity()); + } + + void AppendText(const char *text) { + if (logging_utils_class_ == 0) + return; // haven't been initted yet + JNIEnv *env = GetJniEnv(); + assert(env); + jstring text_string = env->NewStringUTF(text); + env->CallStaticVoidMethod(logging_utils_class_, logging_utils_add_log_text_, + text_string); + env->DeleteLocalRef(text_string); + } + +private: + jclass logging_utils_class_; + jmethodID logging_utils_add_log_text_; + jmethodID logging_utils_init_log_window_; +}; + +LoggingUtilsData *g_logging_utils_data; + +// Checks if a JNI exception has happened, and if so, logs it to the console. +void CheckJNIException() { + JNIEnv *env = GetJniEnv(); + if (env->ExceptionCheck()) { + // Get the exception text. + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + + // Convert the exception to a string. + jclass object_class = env->FindClass("java/lang/Object"); + jmethodID toString = + env->GetMethodID(object_class, "toString", "()Ljava/lang/String;"); + jstring s = (jstring)env->CallObjectMethod(exception, toString); + const char *exception_text = env->GetStringUTFChars(s, nullptr); + + // Log the exception text. + __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, + "-------------------JNI exception:"); + __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, "%s", + exception_text); + __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, + "-------------------"); + + // Also, assert fail. + assert(false); + + // In the event we didn't assert fail, clean up. + env->ReleaseStringUTFChars(s, exception_text); + env->DeleteLocalRef(s); + env->DeleteLocalRef(exception); + } +} + +// Log a message that can be viewed in "adb logcat". +void LogMessage(const char *format, ...) { + static const int kLineBufferSize = 100; + char buffer[kLineBufferSize + 2]; + + va_list list; + va_start(list, format); + int string_len = vsnprintf(buffer, kLineBufferSize, format, list); + string_len = string_len < kLineBufferSize ? string_len : kLineBufferSize; + // append a linebreak to the buffer: + buffer[string_len] = '\n'; + buffer[string_len + 1] = '\0'; + + __android_log_vprint(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, format, list); + g_logging_utils_data->AppendText(buffer); + CheckJNIException(); + va_end(list); +} + +// Get the JNI environment. +JNIEnv *GetJniEnv() { + JavaVM *vm = g_app_state->activity->vm; + JNIEnv *env; + jint result = vm->AttachCurrentThread(&env, nullptr); + return result == JNI_OK ? env : nullptr; +} + +// Execute common_main(), flush pending events and finish the activity. +extern "C" void android_main(struct android_app *state) { + // native_app_glue spawns a new thread, calling android_main() when the + // activity onStart() or onRestart() methods are called. This code handles + // the case where we're re-entering this method on a different thread by + // signalling the existing thread to exit, waiting for it to complete before + // reinitializing the application. + if (g_started) { + g_restarted = true; + // Wait for the existing thread to exit. + pthread_mutex_lock(&g_started_mutex); + pthread_mutex_unlock(&g_started_mutex); + } else { + g_started_mutex = PTHREAD_MUTEX_INITIALIZER; + } + pthread_mutex_lock(&g_started_mutex); + g_started = true; + + // Save native app glue state and setup a callback to track the state. + g_destroy_requested = false; + g_app_state = state; + g_app_state->onAppCmd = OnAppCmd; + + // Create the logging display. + g_logging_utils_data = new LoggingUtilsData(); + g_logging_utils_data->Init(); + + // Execute cross platform entry point. + static const char *argv[] = {FIREBASE_TESTAPP_NAME}; + int return_value = common_main(1, argv); + (void)return_value; // Ignore the return value. + ProcessEvents(10); + + // Clean up logging display. + delete g_logging_utils_data; + g_logging_utils_data = nullptr; + + // Finish the activity. + if (!g_restarted) + ANativeActivity_finish(state->activity); + + g_app_state->activity->vm->DetachCurrentThread(); + g_started = false; + g_restarted = false; + pthread_mutex_unlock(&g_started_mutex); +} diff --git a/invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java similarity index 91% rename from invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java rename to gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index acbd8d3e..3cb37ecf 100644 --- a/invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -33,6 +33,7 @@ public static void initLogWindow(Activity activity) { LinearLayout linearLayout = new LinearLayout(activity); ScrollView scrollView = new ScrollView(activity); TextView textView = new TextView(activity); + textView.setTag("Logger"); linearLayout.addView(scrollView); scrollView.addView(textView); Window window = activity.getWindow(); @@ -43,12 +44,12 @@ public static void initLogWindow(Activity activity) { public static void addLogText(final String text) { new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (sTextView != null) { - sTextView.append(text); - } + @Override + public void run() { + if (sTextView != null) { + sTextView.append(text); } - }); + } + }); } } diff --git a/gma/testapp/src/common_main.cc b/gma/testapp/src/common_main.cc new file mode 100644 index 00000000..10a0a181 --- /dev/null +++ b/gma/testapp/src/common_main.cc @@ -0,0 +1,473 @@ +// Copyright 2022 Google Inc. 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 "firebase/app.h" +#include "firebase/future.h" +#include "firebase/gma.h" +#include "firebase/gma/ad_view.h" +#include "firebase/gma/interstitial_ad.h" +#include "firebase/gma/rewarded_ad.h" +#include "firebase/gma/types.h" + +// Thin OS abstraction layer. +#include "main.h" // NOLINT + +// A simple listener that logs changes to an AdView. +class LoggingAdViewListener : public firebase::gma::AdListener { +public: + LoggingAdViewListener() {} + + void OnAdClicked() override { ::LogMessage("AdView ad clicked."); } + + void OnAdClosed() override { ::LogMessage("AdView ad closed."); } + + void OnAdImpression() override { ::LogMessage("AdView ad impression."); } + + void OnAdOpened() override { ::LogMessage("AdView ad opened."); } +}; + +// A simple listener that logs changes to an AdView's bounding box. +class LoggingAdViewBoundedBoxListener + : public firebase::gma::AdViewBoundingBoxListener { +public: + void OnBoundingBoxChanged(firebase::gma::AdView *ad_view, + firebase::gma::BoundingBox box) override { + ::LogMessage("AdView bounding box update x: %d y: %d " + "width: %d height: %d", + box.x, box.y, box.width, box.height); + } +}; + +// A simple listener track FullScreen content changes. +class LoggingFullScreenContentListener + : public firebase::gma::FullScreenContentListener { +public: + LoggingFullScreenContentListener() : num_ad_dismissed_(0) {} + + void OnAdClicked() override { ::LogMessage("FullScreenContent ad clicked."); } + + void OnAdDismissedFullScreenContent() override { + ::LogMessage("FullScreenContent ad dismissed."); + num_ad_dismissed_++; + } + + void OnAdFailedToShowFullScreenContent( + const firebase::gma::AdError &ad_error) override { + ::LogMessage("FullScreenContent ad failed to show full screen content," + " AdErrorCode: %d", + ad_error.code()); + } + + void OnAdImpression() override { + ::LogMessage("FullScreenContent ad impression."); + } + + void OnAdShowedFullScreenContent() override { + ::LogMessage("FullScreenContent ad showed content."); + } + + uint32_t num_ad_dismissed() const { return num_ad_dismissed_; } + +private: + uint32_t num_ad_dismissed_; +}; + +// A simple listener track UserEarnedReward events. +class LoggingUserEarnedRewardListener + : public firebase::gma::UserEarnedRewardListener { +public: + LoggingUserEarnedRewardListener() {} + + void OnUserEarnedReward(const firebase::gma::AdReward &reward) override { + ::LogMessage("User earned reward amount: %d type: %s", reward.amount(), + reward.type().c_str()); + } +}; + +// A simple listener track ad pay events. +class LoggingPaidEventListener : public firebase::gma::PaidEventListener { +public: + LoggingPaidEventListener() {} + + void OnPaidEvent(const firebase::gma::AdValue &value) override { + ::LogMessage("PaidEvent value: %lld currency_code: %s", + value.value_micros(), value.currency_code().c_str()); + } +}; + +void LoadAndShowAdView(const firebase::gma::AdRequest &ad_request); +void LoadAndShowInterstitialAd(const firebase::gma::AdRequest &ad_request); +void LoadAndShowRewardedAd(const firebase::gma::AdRequest &ad_request); + +// 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"; +#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"; +#endif + +// Sample keywords to use in making the request. +static const std::vector kKeywords({"GMA", "C++", "Fun"}); + +// Sample test device IDs to use in making the request. Add your own here. +const std::vector kTestDeviceIDs = { + "2077ef9a63d2b398840261c8221a0c9b", "098fe087d987c9a878965454a65654d7"}; + +#if defined(ANDROID) +static const char *kAdNetworkExtrasClassName = + "com/google/ads/mediation/admob/AdMobAdapter"; +#else +static const char *kAdNetworkExtrasClassName = "GADExtras"; +#endif + +// Function to wait for the completion of a future, and log the error +// if one is encountered. +static void WaitForFutureCompletion(firebase::FutureBase future) { + while (!ProcessEvents(1000)) { + if (future.status() != firebase::kFutureStatusPending) { + break; + } + } + + if (future.error() != firebase::gma::kAdErrorCodeNone) { + LogMessage("ERROR: Action failed with error code %d and message \"%s\".", + future.error(), future.error_message()); + } +} + +// Inittialize GMA, load a Banner, Interstitial and Rewarded Ad. +extern "C" int common_main(int argc, const char *argv[]) { + firebase::App *app; + LogMessage("Initializing Firebase App."); + +#if defined(__ANDROID__) + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); +#else + app = ::firebase::App::Create(); +#endif // defined(__ANDROID__) + + LogMessage("Created the Firebase App %x.", + static_cast(reinterpret_cast(app))); + + LogMessage("Initializing the GMA with Firebase API."); + firebase::gma::Initialize(*app); + + WaitForFutureCompletion(firebase::gma::InitializeLastResult()); + if (firebase::gma::InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Initialization Failure. The error was already logged in + // WaitForFutureCompletion, so simply exit here. + return -1; + } + + // Log mediation adapter initialization status. + for (auto adapter_status : + firebase::gma::GetInitializationStatus().GetAdapterStatusMap()) { + LogMessage( + "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()); + } + + // Configure test device ids before loading ads. + // + // 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 AdMob 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; + firebase::gma::SetRequestConfiguration(request_configuration); + + // + // Load and Display a Banner Ad using AdView. + // + + // Create an AdRequest. + firebase::gma::AdRequest ad_request; + + // Configure additional keywords to be used in targeting. + for (auto keyword_iter = kKeywords.begin(); keyword_iter != kKeywords.end(); + ++keyword_iter) { + ad_request.add_keyword((*keyword_iter).c_str()); + } + + // "Extra" key value pairs can be added to the request as well. Typically + // these are used when testing new features. + ad_request.add_extra(kAdNetworkExtrasClassName, "the_name_of_an_extra", + "the_value_for_that_extra"); + + LoadAndShowAdView(ad_request); + LoadAndShowInterstitialAd(ad_request); + LoadAndShowRewardedAd(ad_request); + + LogMessage("\nAll ad operations complete, terminating GMA"); + + firebase::gma::Terminate(); + delete app; + + // Wait until the user kills the app. + while (!ProcessEvents(1000)) { + } + + return 0; +} + +void LoadAndShowAdView(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show a banner ad in an AdView:"); + LogMessage("==="); + // Initialize an AdView. + firebase::gma::AdView *ad_view = new firebase::gma::AdView(); + const firebase::gma::AdSize banner_ad_size = firebase::gma::AdSize::kBanner; + ad_view->Initialize(GetWindowContext(), kBannerAdUnit, banner_ad_size); + + // Block until the ad view completes initialization. + WaitForFutureCompletion(ad_view->InitializeLastResult()); + + // Check for errors. + if (ad_view->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + LogMessage("AdView initalization failed, error code: %d", + ad_view->InitializeLastResult().error()); + delete ad_view; + ad_view = nullptr; + return; + } + + // Setup the AdView's listeners. + LoggingAdViewListener ad_view_listener; + ad_view->SetAdListener(&ad_view_listener); + LoggingPaidEventListener paid_event_listener; + ad_view->SetPaidEventListener(&paid_event_listener); + LoggingAdViewBoundedBoxListener bounding_box_listener; + ad_view->SetBoundingBoxListener(&bounding_box_listener); + + // Load an ad. + ad_view->LoadAd(ad_request); + WaitForFutureCompletion(ad_view->LoadAdLastResult()); + + // Check for errors. + if (ad_view->LoadAdLastResult().error() != firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + ad_view->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage("AdView::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + WaitForFutureCompletion(ad_view->Destroy()); + delete ad_view; + ad_view = nullptr; + return; + } + + // Log the loaded ad's dimensions. + const firebase::gma::AdSize ad_size = ad_view->ad_size(); + LogMessage("AdView loaded ad width: %d height: %d", ad_size.width(), + ad_size.height()); + + // Show the ad. + LogMessage("Showing the banner ad."); + WaitForFutureCompletion(ad_view->Show()); + + // Move to each of the six pre-defined positions. + LogMessage("Moving the banner ad to top-center."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTop); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to top-left."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTopLeft); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to top-right."); + ad_view->SetPosition(firebase::gma::AdView::kPositionTopRight); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-center."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottom); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-left."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomLeft); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to bottom-right."); + ad_view->SetPosition(firebase::gma::AdView::kPositionBottomRight); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + // Try some coordinate moves. + LogMessage("Moving the banner ad to (100, 300)."); + ad_view->SetPosition(100, 300); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + LogMessage("Moving the banner ad to (100, 400)."); + ad_view->SetPosition(100, 400); + WaitForFutureCompletion(ad_view->SetPositionLastResult()); + + // Try hiding and showing the BannerView. + LogMessage("Hiding the banner ad."); + ad_view->Hide(); + WaitForFutureCompletion(ad_view->HideLastResult()); + + LogMessage("Showing the banner ad."); + ad_view->Show(); + WaitForFutureCompletion(ad_view->ShowLastResult()); + + LogMessage("Hiding the banner ad again now that we're done with it."); + ad_view->Hide(); + WaitForFutureCompletion(ad_view->HideLastResult()); + + // Clean up the ad view. + ad_view->Destroy(); + WaitForFutureCompletion(ad_view->DestroyLastResult()); + delete ad_view; + ad_view = nullptr; +} + +void LoadAndShowInterstitialAd(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show an interstitial ad:"); + LogMessage("==="); + // Initialize an InterstitialAd. + firebase::gma::InterstitialAd *interstitial_ad = + new firebase::gma::InterstitialAd(); + interstitial_ad->Initialize(GetWindowContext()); + + // Block until the interstitial ad completes initialization. + WaitForFutureCompletion(interstitial_ad->InitializeLastResult()); + + // Check for errors. + if (interstitial_ad->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + delete interstitial_ad; + interstitial_ad = nullptr; + return; + } + + // Setup the interstitial ad's listeners. + LoggingFullScreenContentListener fullscreen_content_listener; + interstitial_ad->SetFullScreenContentListener(&fullscreen_content_listener); + LoggingPaidEventListener paid_event_listener; + interstitial_ad->SetPaidEventListener(&paid_event_listener); + + // Load an ad. + interstitial_ad->LoadAd(kInterstitialAdUnit, ad_request); + WaitForFutureCompletion(interstitial_ad->LoadAdLastResult()); + + // Check for errors. + if (interstitial_ad->LoadAdLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + interstitial_ad->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage( + "InterstitialAd::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + delete interstitial_ad; + interstitial_ad = nullptr; + return; + } + + // Show the ad. + LogMessage("Showing the interstitial ad."); + interstitial_ad->Show(); + WaitForFutureCompletion(interstitial_ad->ShowLastResult()); + + // Wait for the user to close the interstitial. + while (fullscreen_content_listener.num_ad_dismissed() == 0) { + ProcessEvents(1000); + } + + // Clean up the interstitial ad. + delete interstitial_ad; + interstitial_ad = nullptr; +} + +// WIP +void LoadAndShowRewardedAd(const firebase::gma::AdRequest &ad_request) { + LogMessage("\nLoad and show a rewarded ad:"); + LogMessage("==="); + // Initialize a RewardedAd. + firebase::gma::RewardedAd *rewarded_ad = new firebase::gma::RewardedAd(); + rewarded_ad->Initialize(GetWindowContext()); + + // Block until the interstitial ad completes initialization. + WaitForFutureCompletion(rewarded_ad->InitializeLastResult()); + + // Check for errors. + if (rewarded_ad->InitializeLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + delete rewarded_ad; + rewarded_ad = nullptr; + return; + } + + // Setup the rewarded ad's lifecycle listeners. + LoggingFullScreenContentListener fullscreen_content_listener; + rewarded_ad->SetFullScreenContentListener(&fullscreen_content_listener); + LoggingPaidEventListener paid_event_listener; + rewarded_ad->SetPaidEventListener(&paid_event_listener); + + // Load an ad. + rewarded_ad->LoadAd(kRewardedAdUnit, ad_request); + WaitForFutureCompletion(rewarded_ad->LoadAdLastResult()); + + // Check for errors. + if (rewarded_ad->LoadAdLastResult().error() != + firebase::gma::kAdErrorCodeNone) { + // Log information as to why the loadAd request failed. + const firebase::gma::AdResult *result_ptr = + rewarded_ad->LoadAdLastResult().result(); + if (result_ptr != nullptr) { + LogMessage("RewardedAd::loadAd Failure - Code: %d Message: %s Domain: %s", + result_ptr->ad_error().code(), + result_ptr->ad_error().message().c_str(), + result_ptr->ad_error().domain().c_str()); + } + delete rewarded_ad; + rewarded_ad = nullptr; + return; + } + + // Show the ad. + LogMessage("Showing the rewarded ad."); + LoggingUserEarnedRewardListener user_earned_reward_listener; + rewarded_ad->Show(&user_earned_reward_listener); + WaitForFutureCompletion(rewarded_ad->ShowLastResult()); + + // Wait for the user to close the interstitial. + while (fullscreen_content_listener.num_ad_dismissed() == 0) { + ProcessEvents(1000); + } + + // Clean up the interstitial ad. + delete rewarded_ad; + rewarded_ad = nullptr; +} diff --git a/gma/testapp/src/desktop/desktop_main.cc b/gma/testapp/src/desktop/desktop_main.cc new file mode 100644 index 00000000..0a318786 --- /dev/null +++ b/gma/testapp/src/desktop/desktop_main.cc @@ -0,0 +1,124 @@ +// Copyright 2016 Google Inc. 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 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else +#include +#endif // _WIN32 + +#ifdef _WIN32 +#include +#endif // _WIN32 + +#include +#include + +#include "main.h" // NOLINT + +// 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 + +extern "C" int common_main(int argc, const char *argv[]); + +static bool quit = false; + +#ifdef _WIN32 +static BOOL WINAPI SignalHandler(DWORD event) { + if (!(event == CTRL_C_EVENT || event == CTRL_BREAK_EVENT)) { + return FALSE; + } + quit = true; + return TRUE; +} +#else +static void SignalHandler(int /* ignored */) { quit = true; } +#endif // _WIN32 + +bool ProcessEvents(int msec) { +#ifdef _WIN32 + Sleep(msec); +#else + usleep(msec * 1000); +#endif // _WIN32 + return quit; +} + +std::string PathForResource() { return std::string(); } + +void LogMessage(const char *format, ...) { + va_list list; + va_start(list, format); + vprintf(format, list); + va_end(list); + printf("\n"); + fflush(stdout); +} + +WindowContext GetWindowContext() { return nullptr; } + +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char *file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) + chdir(directory.c_str()); + } +} + +int main(int argc, const char *argv[]) { + ChangeToFileDirectory(FIREBASE_CONFIG_STRING[0] != '\0' + ? FIREBASE_CONFIG_STRING + : argv[0]); // NOLINT +#ifdef _WIN32 + SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); +#else + signal(SIGINT, SignalHandler); +#endif // _WIN32 + return common_main(argc, argv); +} + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/admob/testapp/src/ios/ios_main.mm b/gma/testapp/src/ios/ios_main.mm similarity index 74% rename from admob/testapp/src/ios/ios_main.mm rename to gma/testapp/src/ios/ios_main.mm index 6ccb2de5..4c226d5e 100644 --- a/admob/testapp/src/ios/ios_main.mm +++ b/gma/testapp/src/ios/ios_main.mm @@ -18,9 +18,9 @@ #include "main.h" -extern "C" int common_main(int argc, const char* argv[]); +extern "C" int common_main(int argc, const char *argv[]); -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property(nonatomic, strong) UIWindow *window; @@ -42,33 +42,35 @@ @implementation FTAViewController - (void)viewDidLoad { [super viewDidLoad]; g_parent_view = self.view; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - const char *argv[] = {FIREBASE_TESTAPP_NAME}; - [g_shutdown_signal lock]; - g_exit_status = common_main(1, argv); - [g_shutdown_complete signal]; - }); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), + ^{ + const char *argv[] = {FIREBASE_TESTAPP_NAME}; + [g_shutdown_signal lock]; + g_exit_status = common_main(1, argv); + [g_shutdown_complete signal]; + }); } @end bool ProcessEvents(int msec) { [g_shutdown_signal - waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:static_cast(msec) / 1000.0f]]; + waitUntilDate:[NSDate + dateWithTimeIntervalSinceNow:static_cast(msec) / + 1000.0f]]; return g_shutdown; } -WindowContext GetWindowContext() { - return g_parent_view; -} +WindowContext GetWindowContext() { return g_parent_view; } // Log a message that can be viewed in the console. -void LogMessage(const char* format, ...) { +void LogMessage(const char *format, ...) { va_list args; NSString *formatString = @(format); va_start(args, format); - NSString *message = [[NSString alloc] initWithFormat:formatString arguments:args]; + NSString *message = [[NSString alloc] initWithFormat:formatString + arguments:args]; va_end(args); NSLog(@"%@", message); @@ -79,7 +81,7 @@ void LogMessage(const char* format, ...) { }); } -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { @autoreleasepool { UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } @@ -88,8 +90,8 @@ int main(int argc, char* argv[]) { @implementation AppDelegate -- (BOOL)application:(UIApplication*)application - didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { g_shutdown_complete = [[NSCondition alloc] init]; g_shutdown_signal = [[NSCondition alloc] init]; [g_shutdown_complete lock]; diff --git a/gma/testapp/src/main.h b/gma/testapp/src/main.h new file mode 100644 index 00000000..d41017a7 --- /dev/null +++ b/gma/testapp/src/main.h @@ -0,0 +1,63 @@ +// Copyright 2016 Google Inc. 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. + +#ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#define FIREBASE_TESTAPP_MAIN_H_ // NOLINT + +#if defined(__ANDROID__) +#include +#include +#elif defined(__APPLE__) +extern "C" { +#include +} // extern "C" +#endif // __ANDROID__ + +// Defined using -DANDROID_MAIN_APP_NAME=some_app_name when compiling this +// file. +#ifndef FIREBASE_TESTAPP_NAME +#define FIREBASE_TESTAPP_NAME "android_main" +#endif // FIREBASE_TESTAPP_NAME + +// Cross platform logging method. +// Implemented by android/android_main.cc or ios/ios_main.mm. +extern "C" void LogMessage(const char *format, ...); + +// Platform-independent method to flush pending events for the main thread. +// Returns true when an event requesting program-exit is received. +bool ProcessEvents(int msec); + +// WindowContext represents the handle to the parent window. It's type +// (and usage) vary based on the OS. +#if defined(__ANDROID__) +typedef jobject WindowContext; // A jobject to the Java Activity. +#elif defined(__APPLE__) +typedef id WindowContext; // A pointer to an iOS UIView. +#else +typedef void *WindowContext; // A void* for any other environments. +#endif + +#if defined(__ANDROID__) +// Get the JNI environment. +JNIEnv *GetJniEnv(); +// Get the activity. +jobject GetActivity(); +#endif // defined(__ANDROID__) + +// Returns a variable that describes the window context for the app. On Android +// this will be a jobject pointing to the Activity. On iOS, it's an id pointing +// to the root view of the view controller. +WindowContext GetWindowContext(); + +#endif // FIREBASE_TESTAPP_MAIN_H_ // NOLINT diff --git a/admob/testapp/testapp.xcodeproj/project.pbxproj b/gma/testapp/testapp.xcodeproj/project.pbxproj similarity index 99% rename from admob/testapp/testapp.xcodeproj/project.pbxproj rename to gma/testapp/testapp.xcodeproj/project.pbxproj index 14a0af7f..3afd2dea 100644 --- a/admob/testapp/testapp.xcodeproj/project.pbxproj +++ b/gma/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/admob/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/gma/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from admob/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json rename to gma/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json diff --git a/admob/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/gma/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json similarity index 100% rename from admob/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json rename to gma/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json diff --git a/admob/testapp/testapp/Info.plist b/gma/testapp/testapp/Info.plist similarity index 91% rename from admob/testapp/testapp/Info.plist rename to gma/testapp/testapp/Info.plist index 4744d9dc..3f0f944e 100644 --- a/admob/testapp/testapp/Info.plist +++ b/gma/testapp/testapp/Info.plist @@ -2,6 +2,8 @@ + GADApplicationIdentifier + YOUR_IOS_ADMOB_APP_ID CFBundleDevelopmentRegion en CFBundleExecutable @@ -16,8 +18,6 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleVersion 1 LSRequiresIPhoneOS diff --git a/invites/testapp/Podfile b/invites/testapp/Podfile deleted file mode 100644 index ee5d54c6..00000000 --- a/invites/testapp/Podfile +++ /dev/null @@ -1,6 +0,0 @@ -source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' -# Invites test application. -target 'testapp' do - pod 'Firebase/Invites' -end diff --git a/invites/testapp/build.gradle b/invites/testapp/build.gradle deleted file mode 100644 index 668f0496..00000000 --- a/invites/testapp/build.gradle +++ /dev/null @@ -1,119 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - mavenLocal() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' - } -} - -allprojects { - repositories { - mavenLocal() - jcenter() - } -} - -apply plugin: 'com.android.application' - -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - -android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' - - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } - } - - defaultConfig { - applicationId 'com.google.android.invites.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' - } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/invites.pro") - proguardFile file('proguard.pro') - } - } -} - -dependencies { - compile 'com.google.firebase:firebase-invites:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' -} - -apply plugin: 'com.google.gms.google-services' - - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/invites/testapp/jni/Android.mk b/invites/testapp/jni/Android.mk deleted file mode 100644 index 7cf2588a..00000000 --- a/invites/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_invites -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libinvites.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_invites \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/invites/testapp/jni/Application.mk b/invites/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/invites/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/invites/testapp/readme.md b/invites/testapp/readme.md deleted file mode 100644 index 528236ce..00000000 --- a/invites/testapp/readme.md +++ /dev/null @@ -1,176 +0,0 @@ -Firebase Invites Quickstart -============================== - -The Firebase Invites Test Application (testapp) demonstrates both -sending and receiving Firebase Invites using the Firebase Invites C++ -SDK. This application has no user interface and simply logs actions -it's performing to the console. - -Introduction ------------- - -- [Read more about Firebase Invites](https://firebase.google.com/docs/invites/) -- [Read more about Firebase Dynamic Links](https://firebase.google.com/docs/dynamic-links/) - -Building and Running the testapp --------------------------------- - -### iOS - - Link your iOS app to the Firebase libraries. - - Get CocoaPods version 1 or later by running, - ``` - $ sudo gem install CocoaPods --pre - ``` - - From the testapp directory, install the CocoaPods listed in the Podfile - by running, - ``` - $ pod install - ``` - - Open the generated Xcode workspace (which now has the CocoaPods), - ``` - $ open testapp.xcworkspace - ``` - - For further details please refer to the - [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). - - Register your iOS app with Firebase. - - Create a new app on - [firebase.google.com/console](https://firebase.google.com/console/), - and attach your iOS app to it. - - For Invites, you will need an App Store ID. Use something random such - as 12345678." - - You can use "com.google.ios.invites.testapp" as the iOS Bundle ID - while you're testing. - - Add the GoogleService-Info.plist that you downloaded from Firebase - console to the testapp root directory. This file identifies your iOS app - to the Firebase backend. - - Make sure you set up a URL type to handle the callback. In your project's - Info tab, under the URL Types section, find the URL Schemes box containing - YOUR\_REVERSED\_CLIENT\_ID. Replace this with the value of the - REVERSED\_CLIENT\_ID string in GoogleService-Info.plist. - - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - - Add the following frameworks from the Firebase C++ SDK to the project: - - frameworks/ios/universal/firebase.framework - - frameworks/ios/universal/firebase_invites.framework - - You will need to either, - 1. Check "Copy items if needed" when adding the frameworks, or - 2. Add the framework path in "Framework Search Paths" - - e.g. If you downloaded the Firebase C++ SDK to - `/Users/me/firebase_cpp_sdk`, - then you would add the path - `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. - - To add the path, in XCode, select your project in the project - navigator, then select your target in the main window. - Select the "Build Settings" tab, and click "All" to see all - the build settings. Scroll down to "Search Paths", and add - your path to "Framework Search Paths". - - _Known Issue_: There is an issue where bundles from the FirebaseInvites - Cocoapod are not properly copied into the Xcode project. To work around the - issue, you can manually copy the bundles into your project. Navigate to the - Pods/FirebaseInvites/Frameworks/frameworks/FirebaseInvites.framework/Resources - subdirectory of your project's folder, and drag the - GINInviteResources.bundle and GPPACLPickerResources.bundle folders into your - Xcode project, selecting "Copy items if needed" when adding them. - - Firebase Invites uses Google Sign-In to send invites, which requires the - Keychain Sharing capability on iOS 10. You can enable this capability on - your project in Xcode 8 by going to your project's settings, Capabilities, - and turning on Keychain Sharing. - - In Xcode, build & run the sample on an iOS device or simulator. - - The testapp has no user interface. The output of the app can be viewed - via the console. In Xcode, select - "View --> Debug Area --> Activate Console" from the menu. - -### Android - - Register your Android app with Firebase. - - Create a new app on - [developers.google.com](https://firebase.google.com/console/), - and attach your Android app to it. - - You can use "com.google.android.invites.testapp" as the Package Name - while you're testing. - - To [generate a SHA1](https://developers.google.com/android/guides/client-auth) - run this command on Mac and Linux, - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore - ``` - or this command on Windows, - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore - ``` - - If keytool reports that you do not have a debug.keystore, you can - [create one with](http://developer.android.com/tools/publishing/app-signing.html#signing-manually), - ``` - keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" - ``` - - Add the `google-services.json` file that you downloaded from Firebase - console to the root directory of testapp. This file identifies your - Android app to the Firebase backend. - - For further details please refer to the - [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - - Configure the location of the Firebase C++ SDK by setting the - firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. - For example, in the project directory: - ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties - ``` - - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. - - Open *build.gradle* in Android Studio. - - From the Android Studio launch menu, "Open an existing Android Studio - project", and select `build.gradle`. - - Install the SDK Platforms that Android Studio reports missing. - - Build the testapp and run it on an Android device or emulator. - - See [below](#using_the_test_app) for usage instructions. - -# Using the Test App - -- Install and run the test app on your iOS or Android device or emulator. -- The application has minimal user interface. The output of the app can be viewed - via the console: - - __iOS__: Open select "View --> Debug Area --> Activate Console" from the menu - in Xcode. - - __Android__: View the logcat output in Android studio or by running - "adb logcat" from the command line. - -- When you first run the app, it will check for an incoming dynamic link or - invitation, and report whether it was able to fetch an invite. -- Afterwards, it will open a screen that allows you to send an invite for the - current app via e-mail or SMS. - - You may have to log in to Google first. -- To simulate receiving an invitation from a friend, you can send yourself an - invite, uninstall the test app, then click the link in your e-mail. - - This would normally send you to the Play Store or App Store to download the - app. Because this is a test app, it will link to a nonexistent store page. -- After clicking the invite link, re-install and run the app on your device or - emulator, and see the invitation fetched on the receiving side. - -Support -------- - -https://firebase.google.com/support/ - -License -------- - -Copyright 2016 Google, Inc. - -Licensed to the Apache Software Foundation (ASF) under one or more contributor -license agreements. See the NOTICE file distributed with this work for -additional information regarding copyright ownership. The ASF licenses this -file to you 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. - diff --git a/invites/testapp/src/common_main.cc b/invites/testapp/src/common_main.cc deleted file mode 100644 index c585142e..00000000 --- a/invites/testapp/src/common_main.cc +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2016 Google Inc. 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 "firebase/app.h" -#include "firebase/future.h" -#include "firebase/invites.h" -#include "firebase/util.h" - -// Thin OS abstraction layer. -#include "main.h" // NOLINT - -void ConversionFinished(const firebase::Future& future_result, - void* user_data) { - if (future_result.status() == firebase::kFutureStatusInvalid) { - LogMessage("ConvertInvitation: Invalid, sorry!"); - } else if (future_result.status() == firebase::kFutureStatusComplete) { - LogMessage("ConvertInvitation: Complete!"); - if (future_result.error() != 0) { - LogMessage("ConvertInvitation: Error %d: %s", future_result.error(), - future_result.error_message()); - } else { - LogMessage("ConvertInvitation: Successfully converted invitation"); - } - } -} - -class InviteListener : public firebase::invites::Listener { - public: - void OnInviteReceived(const char* invitation_id, const char* deep_link, - bool is_strong_match) override { // NOLINT - if (invitation_id != nullptr) { - LogMessage("InviteReceived: Got invitation ID: %s", invitation_id); - - // We got an invitation ID, so let's try and convert it. - LogMessage("ConvertInvitation: Converting invitation %s", invitation_id); - - ::firebase::invites::ConvertInvitation(invitation_id) - .OnCompletion(ConversionFinished, nullptr); - } - if (deep_link != nullptr) { - LogMessage("InviteReceived: Got deep link: %s", deep_link); - } - } - - void OnInviteNotReceived() override { - LogMessage("InviteReceived: No invitation ID or deep link, confirmed."); - } - - void OnErrorReceived(int error_code, const char* error_message) override { - LogMessage("Error (%d) on received invite: %s", error_code, error_message); - } -}; - -InviteListener g_listener; - -// Execute all methods of the C++ Invites API. -extern "C" int common_main(int argc, const char* argv[]) { - ::firebase::App* app; - - LogMessage("Initializing Firebase App"); - -#if defined(__ANDROID__) - app = ::firebase::App::Create(GetJniEnv(), GetActivity()); -#else - app = ::firebase::App::Create(); -#endif // defined(__ANDROID__) - - LogMessage("Created the Firebase App %x", - static_cast(reinterpret_cast(app))); - - ::firebase::ModuleInitializer initializer; - initializer.Initialize(app, nullptr, [](::firebase::App* app, void*) { - LogMessage("Try to initialize Invites"); - return ::firebase::invites::Initialize(*app); - }); - while (initializer.InitializeLastResult().status() != - firebase::kFutureStatusComplete) { - if (ProcessEvents(100)) return 1; // exit if requested - } - if (initializer.InitializeLastResult().error() != 0) { - LogMessage("Failed to initialize Firebase Invites: %s", - initializer.InitializeLastResult().error_message()); - ProcessEvents(2000); - return 1; - } - LogMessage("Initialized Firebase Invites."); - - // First, try sending an Invite. - { - LogMessage("SendInvite: Sending an invitation..."); - ::firebase::invites::Invite invite; - invite.title_text = "Invites Test App"; - invite.message_text = "Please try my app! It's awesome."; - invite.call_to_action_text = "Download it for FREE"; - invite.deep_link_url = "http://google.com/abc"; - auto future_result = ::firebase::invites::SendInvite(invite); - while (future_result.status() == firebase::kFutureStatusPending) { - if (ProcessEvents(10)) break; - } - - if (future_result.status() == firebase::kFutureStatusInvalid) { - LogMessage("SendInvite: Invalid, sorry!"); - } else if (future_result.status() == firebase::kFutureStatusComplete) { - LogMessage("SendInvite: Complete!"); - if (future_result.error() != 0) { - LogMessage("SendInvite: Error %d: %s", future_result.error(), - future_result.error_message()); - } else { - auto result = *future_result.result(); - // error == 0 - if (result.invitation_ids.size() == 0) { - LogMessage("SendInvite: Nothing sent, user must have canceled."); - } else { - LogMessage("SendInvite: %d invites sent successfully.", - result.invitation_ids.size()); - for (int i = 0; i < result.invitation_ids.size(); i++) { - LogMessage("SendInvite: Invite code: %s", - result.invitation_ids[i].c_str()); - } - } - } - } - } - - // Then, set the listener, which will check for any invitations. - ::firebase::invites::SetListener(&g_listener); - - LogMessage("Listener set, main loop finished."); - - while (!ProcessEvents(1000)) { - } - - ::firebase::invites::Terminate(); - delete app; - app = nullptr; - - return 0; -} diff --git a/invites/testapp/testapp/Info.plist b/invites/testapp/testapp/Info.plist deleted file mode 100644 index 1590f803..00000000 --- a/invites/testapp/testapp/Info.plist +++ /dev/null @@ -1,57 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - com.google.ios.invites.testapp - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - 1.0 - CFBundleSignature - ???? - CFBundleURLSchemes - - com.google.ios.invites.testapp - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - com.google.ios.invites.testapp - CFBundleURLSchemes - - com.google.ios.invites.testapp - - - - CFBundleTypeRole - Editor - CFBundleURLName - google - CFBundleURLSchemes - - YOUR_REVERSED_CLIENT_ID - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UILaunchStoryboardName - LaunchScreen - NSContactsUsageDescription - Invite others to use the app. - - diff --git a/messaging/testapp/AndroidManifest.xml b/messaging/testapp/AndroidManifest.xml index 44c2159d..c0357769 100644 --- a/messaging/testapp/AndroidManifest.xml +++ b/messaging/testapp/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.google.android.messaging.testapp" android:versionCode="1" android:versionName="1.0"> - + @@ -19,6 +19,7 @@ work around a known issue when receiving notification data payloads in the background. --> @@ -28,26 +29,32 @@ - - + - + + + + + - + - - + diff --git a/messaging/testapp/CMakeLists.txt b/messaging/testapp/CMakeLists.txt new file mode 100644 index 00000000..03bd733e --- /dev/null +++ b/messaging/testapp/CMakeLists.txt @@ -0,0 +1,118 @@ +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase samples. +# 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() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +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() + +# 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) + +# Sample source files. +set(FIREBASE_SAMPLE_COMMON_SRCS + src/main.h + src/common_main.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Sample uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_SAMPLE_ANDROID_SRCS + src/android/android_main.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") + + # Define the target as a shared library, as that is what gradle expects. + set(target_name "android_main") + add_library(${target_name} SHARED + ${FIREBASE_SAMPLE_ANDROID_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + target_link_libraries(${target_name} + log android atomic native_app_glue + ) + + target_include_directories(${target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS) +else() + # Build a desktop application. + + # 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 sample. + set(FIREBASE_SAMPLE_DESKTOP_SRCS + src/desktop/desktop_main.cc + ) + + set(target_name "desktop_testapp") + add_executable(${target_name} + ${FIREBASE_SAMPLE_DESKTOP_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + if(APPLE) + set(ADDITIONAL_LIBS pthread) + elseif(MSVC) + set(ADDITIONAL_LIBS) + 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 ${config}) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${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_messaging firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/messaging/testapp/Podfile b/messaging/testapp/Podfile index ce69f3b8..b022880f 100644 --- a/messaging/testapp/Podfile +++ b/messaging/testapp/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # FCM test application. target 'testapp' do - pod 'Firebase/Messaging' + pod 'Firebase/Messaging', '10.25.0' end diff --git a/messaging/testapp/build.gradle b/messaging/testapp/build.gradle index 520d791f..4f607fe3 100644 --- a/messaging/testapp/build.gradle +++ b/messaging/testapp/build.gradle @@ -2,124 +2,75 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.android.messaging.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.android.messaging.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/messaging.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } -} - -repositories { - flatDir { - dirs project.ext.firebase_cpp_sdk_dir + "/libs/android" + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false } } -dependencies { - compile 'com.google.firebase.messaging.cpp:firebase_messaging_cpp@aar' - compile 'com.google.firebase:firebase-messaging:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + messaging } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/messaging/testapp/gradle.properties b/messaging/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/messaging/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/messaging/testapp/gradle/wrapper/gradle-wrapper.properties b/messaging/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/messaging/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/messaging/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/messaging/testapp/jni/Android.mk b/messaging/testapp/jni/Android.mk deleted file mode 100644 index 15ae2801..00000000 --- a/messaging/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_messaging -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libmessaging.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_messaging \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/messaging/testapp/jni/Application.mk b/messaging/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/messaging/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/messaging/testapp/proguard.pro b/messaging/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/messaging/testapp/proguard.pro +++ b/messaging/testapp/proguard.pro @@ -1 +1,2 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/messaging/testapp/readme.md b/messaging/testapp/readme.md index 59d3e4f2..c5a59969 100644 --- a/messaging/testapp/readme.md +++ b/messaging/testapp/readme.md @@ -1,167 +1,48 @@ Firebase Cloud Messaging Quickstart =================================== -The Firebase Cloud Messaging Test Application (testapp) demonstrates receiving -Firebase Cloud Messages using the Firebase Cloud Messaging C++ SDK. This +The Firebase Messaging C++ Sample (hereafter the 'test app') demonstrates receiving messages from [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) +using the [Firebase C++ SDK](https://firebase.google.com/docs/cpp/setup). This application has no user interface and simply logs actions it's performing to the console. -Introduction ------------- +## Set up, build and run the test app -- [Read more about Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging/) +> **_NOTE:_** Do not implement the code changes of the subsequent linked instructions as this codebase already has those functionalities. -Building and Running the testapp --------------------------------- -### iOS - - Link your iOS app to the Firebase libraries. - - Get CocoaPods version 1 or later by running, +1. Complete [Add Firebase to your C++ project](https://firebase.google.com/docs/cpp/setup) for your selected build platforms. +1. [Set up a Firebase Cloud Messaging client app with C++](https://firebase.google.com/docs/cloud-messaging/cpp/client). - ``` - $ sudo gem install CocoaPods --pre - ``` - - From the testapp directory, install the CocoaPods listed in the Podfile by - running, - ``` - $ pod install - ``` - - Open the generated Xcode workspace (which now has the CocoaPods), - - ``` - $ open testapp.xcworkspace - ``` - - For further details please refer to the - [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). - - - Register your iOS app with Firebase. - - Create a new app on - [firebase.google.com/console](https://firebase.google.com/console/) - , and attach your iOS app to it. - - For Messaging, you will need an App Store ID. Use something random such - as 12345678." - - You can use "com.google.ios.messaging.testapp" as the iOS Bundle ID - while you're testing. - - Add the GoogleService-Info.plist that you downloaded from Firebase - console to the testapp root directory. This file identifies your iOS app - to the Firebase backend. - - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - - Add the following frameworks from the Firebase C++ SDK to the project: - - frameworks/ios/universal/firebase.framework - - frameworks/ios/universal/firebase_messaging.framework - - You will need to either, - 1. Check "Copy items if needed" when adding the frameworks, or - 2. Add the framework path in "Framework Search Paths" - - e.g. If you downloaded the Firebase C++ SDK to - `/Users/me/firebase_cpp_sdk`, - then you would add the path - `/Users/me/firebase_cpp_sdk/frameworks/ios/universal`. - - To add the path, in XCode, select your project in the project - navigator, then select your target in the main window. - Select the "Build Settings" tab, and click "All" to see all - the build settings. Scroll down to "Search Paths", and add - your path to "Framework Search Paths". - - You need a valid - [APNs](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html) - certificate. If you don't already have one, see - [Provisioning APNs SSL Certificates.](https://firebase.google.com/docs/cloud-messaging/ios/certs) - - In XCode, build & run the sample on an iOS device or simulator. - - The testapp has no user interface. The output of the app can be viewed - via the console. In Xcode, select - `View --> Debug Area --> Activate Console` from the menu. - -### Android -**Register your Android app with Firebase.** - -- Create a new app on -[firebase.google.com/console](https://firebase.google.com/console/) -, and attach your Android app to it. -- You can use "com.google.android.messaging.testapp" as the Package Name -while you're testing. -- To [generate a SHA1](https://developers.google.com/android/guides/client-auth) -run this command (_Note: the default password is 'android'_): - * Mac and Linux: - - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore - ``` - * Windows: - - ``` - keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore - ``` -- If keytool reports that you do not have a debug.keystore, you can -[create one](http://developer.android.com/tools/publishing/app-signing.html#signing-manually) -with: - - ``` - keytool -genkey -v -keystore ~/.android/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US" - ``` -- Add the `google-services.json` file that you downloaded from Firebase - console to the root directory of testapp. This file identifies your - Android app to the Firebase backend. -- For further details please refer to the -[general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - -- Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. - -**Configure your SDK paths** - -- Configure the location of the Firebase C++ SDK by setting the -`firebase_cpp_sdk.dir` Gradle property to the SDK install directory. - * Run this command in the project directory, and modify the path to match your - local installation path: - - ``` - > echo "systemProp.firebase_cpp_sdk.dir=/User/$USER/firebase_cpp_sdk" >> gradle.properties - ``` -- Ensure the Android SDK and NDK locations are set in Android Studio. - * From the Android Studio launch menu, go to - `Configure/Project Defaults/Project Structure` and download the SDK and NDK - if the locations are not yet set. - * Android Studio will write these paths to `local.properties`. - -**Build & Run** - -- Open `build.gradle` in Android Studio. - - From the Android Studio launch menu, "Open an existing Android Studio - project", and select `build.gradle`. -- Install the SDK Platforms that Android Studio reports missing. -- Build the testapp and run it on an Android device or emulator. -- See [below](#using_the_test_app) for usage instructions. - -Using the Test App ------------------- +### Running the test app +---------------------- - Install and run the test app on your iOS or Android device or emulator. - The application has minimal user interface. The output of the app can be viewed via the console: - * **iOS**: Open select "View --> Debug Area --> Activate Console" from the + * **iOS**: Open select "View -> Debug Area -> Activate Console" from the menu in Xcode. * **Android**: View the logcat output in Android studio or by running - "adb logcat" from the command line. + `adb logcat` from the command line. - When you first run the app, it will print: -`Recieved Registration Token: `. Copy this code to a text editor. -- Copy the ServerKey from the firebase console: - - Open your project in the - [Firebase Console](https://firebase.google.com/console/). - - Click the gear icon then `Project settings` in the menu on the left - - Select the `Cloud Messaging` tab. - - Copy the `Server Key` -- Replace `` and `` in this command and run it -from the command line. -``` -curl --header "Authorization: key=" --header "Content-Type: application/json" https://android.googleapis.com/gcm/send -d '{"notification":{"title":"Hi","body":"Hello from the Cloud"},"data":{"score":"lots"},"to":""}' -``` -- Observe the command received in the app, via the console output. +`Received Registration Token: `. Copy this code to a text editor. -Support -------- +### Send a message from your server environment +----------------------------------------------- + +1. Navigate to the FCM REST API Docs for [Method: projects.messages.send](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages/send) +1. Look for the "Try this method" panel + 1. This might require you to expand your browser window. +1. Follow the API doc to understand what IDs and fields to fill in for your particular use case. + 1. Your project number is available from [Project settings](https://console.firebase.google.com/project/_/settings/general) +1. Fill in the `Request body` referencing the [FCM REST API docs](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages). +1. Execute. +1. Observe the command received in the app, via the console output. -https://firebase.google.com/support/ +Reminder, while this process is currently done from our website, it uses the FCM v1 Send API directly and provides the simplest starting point to switch to using the v1 Send API or the Firebase Admin SDK in production. + +## Troubleshooting and Support +- [Firebase Cloud Messaging troubleshooting & FAQ](https://firebase.google.com/docs/cloud-messaging/troubleshooting) +- [Firebase Support](https://firebase.google.com/support/) License ------- @@ -182,4 +63,3 @@ 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. - diff --git a/messaging/testapp/settings.gradle b/messaging/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/messaging/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/messaging/testapp/src/android/java/com/google/firebase/example/TestappNativeActivity.java b/messaging/testapp/src/android/java/com/google/firebase/example/TestappNativeActivity.java index 9a2ca519..e504595a 100644 --- a/messaging/testapp/src/android/java/com/google/firebase/example/TestappNativeActivity.java +++ b/messaging/testapp/src/android/java/com/google/firebase/example/TestappNativeActivity.java @@ -4,6 +4,7 @@ import android.app.NativeActivity; import android.content.Intent; +import android.os.Bundle; import com.google.firebase.messaging.MessageForwardingService; /** @@ -15,6 +16,18 @@ * background. */ public class TestappNativeActivity extends NativeActivity { + // The key in the intent's extras that maps to the incoming message's message ID. Only sent by + // the server, GmsCore sends EXTRA_MESSAGE_ID_KEY below. Server can't send that as it would get + // stripped by the client. + private static final String EXTRA_MESSAGE_ID_KEY_SERVER = "message_id"; + + // An alternate key value in the intent's extras that also maps to the incoming message's message + // ID. Used by upstream, and set by GmsCore. + private static final String EXTRA_MESSAGE_ID_KEY = "google.message_id"; + + // The key in the intent's extras that maps to the incoming message's sender value. + private static final String EXTRA_FROM = "google.message_id"; + /** * Workaround for when a message is sent containing both a Data and Notification payload. * @@ -28,10 +41,21 @@ public class TestappNativeActivity extends NativeActivity { */ @Override protected void onNewIntent(Intent intent) { - Intent message = new Intent(this, MessageForwardingService.class); - message.setAction(MessageForwardingService.ACTION_REMOTE_INTENT); - message.putExtras(intent); - startService(message); + // If we do not have a 'from' field this intent was not a message and should not be handled. It + // probably means this intent was fired by tapping on the app icon. + Bundle extras = intent.getExtras(); + String from = extras.getString(EXTRA_FROM); + String messageId = extras.getString(EXTRA_MESSAGE_ID_KEY); + if (messageId == null) { + messageId = extras.getString(EXTRA_MESSAGE_ID_KEY_SERVER); + } + if (from != null && messageId != null) { + Intent message = new Intent(this, MessageForwardingService.class); + message.setAction(MessageForwardingService.ACTION_REMOTE_INTENT); + message.putExtras(intent); + message.setData(intent.getData()); + MessageForwardingService.enqueueWork(this, message); + } setIntent(intent); } } diff --git a/messaging/testapp/src/common_main.cc b/messaging/testapp/src/common_main.cc index 324fcdf5..45a4fca7 100644 --- a/messaging/testapp/src/common_main.cc +++ b/messaging/testapp/src/common_main.cc @@ -13,78 +13,55 @@ // limitations under the License. #include "firebase/app.h" +#include "firebase/future.h" #include "firebase/messaging.h" #include "firebase/util.h" // Thin OS abstraction layer. #include "main.h" // NOLINT -class MessageListener : public firebase::messaging::Listener { - public: - virtual void OnMessage(const ::firebase::messaging::Message& message) { - // When messages are received by the server, they are placed into an - // internal queue, waiting to be consumed. When ProcessMessages is called, - // this OnMessage function is called once for each queued message. - LogMessage("Recieved a new message"); - LogMessage("This message was %s by the user", - message.notification_opened ? "opened" : "not opened"); - if (!message.from.empty()) LogMessage("from: %s", message.from.c_str()); - if (!message.error.empty()) LogMessage("error: %s", message.error.c_str()); - if (!message.message_id.empty()) { - LogMessage("message_id: %s", message.message_id.c_str()); - } - if (!message.data.empty()) { - LogMessage("data:"); - for (const auto& field : message.data) { - LogMessage(" %s: %s", field.first.c_str(), field.second.c_str()); - } - } - if (message.notification) { - LogMessage("notification:"); - if (!message.notification->title.empty()) { - LogMessage(" title: %s", message.notification->title.c_str()); - } - if (!message.notification->body.empty()) { - LogMessage(" body: %s", message.notification->body.c_str()); - } - if (!message.notification->icon.empty()) { - LogMessage(" icon: %s", message.notification->icon.c_str()); - } - if (!message.notification->tag.empty()) { - LogMessage(" tag: %s", message.notification->tag.c_str()); - } - if (!message.notification->color.empty()) { - LogMessage(" color: %s", message.notification->color.c_str()); - } - if (!message.notification->sound.empty()) { - LogMessage(" sound: %s", message.notification->sound.c_str()); - } - if (!message.notification->click_action.empty()) { - LogMessage(" click_action: %s", - message.notification->click_action.c_str()); - } - } +// Don't return until `future` is complete. +// Print a message for whether the result mathes our expectations. +// Returns true if the application should exit. +static bool WaitForFuture(const ::firebase::FutureBase& future, const char* fn, + ::firebase::messaging::Error expected_error, + bool log_error = true) { + // Note if the future has not be started properly. + if (future.status() == ::firebase::kFutureStatusInvalid) { + LogMessage("ERROR: Future for %s is invalid", fn); + return false; } - virtual void OnTokenReceived(const char* token) { - // To send a message to a specific instance of your app a registration token - // is required. These tokens are unique for each instance of the app. When - // messaging::Initialize is called, a request is sent to the Firebase Cloud - // Messaging server to generate a token. When that token is ready, - // OnTokenReceived will be called. The token should be cached locally so - // that a request doesn't need to be generated each time the app is started. - // - // Once a token is generated is should be sent to your app server, which can - // then use it to send messages to users. - LogMessage("Recieved Registration Token: %s", token); + // Wait for future to complete. + LogMessage(" %s...", fn); + while (future.status() == ::firebase::kFutureStatusPending) { + if (ProcessEvents(100)) return true; } -}; -MessageListener g_listener; + // Log error result. + if (log_error) { + const ::firebase::messaging::Error error = + static_cast<::firebase::messaging::Error>(future.error()); + if (error == expected_error) { + const char* error_message = future.error_message(); + if (error_message) { + LogMessage("%s completed as expected", fn); + } else { + LogMessage("%s completed as expected, error: %d '%s'", fn, error, + error_message); + } + } else { + LogMessage("ERROR: %s completed with error: %d, `%s`", fn, error, + future.error_message()); + } + } + return false; +} // Execute all methods of the C++ Firebase Cloud Messaging API. extern "C" int common_main(int argc, const char* argv[]) { ::firebase::App* app; + ::firebase::messaging::PollableListener listener; #if defined(__ANDROID__) app = ::firebase::App::Create(GetJniEnv(), GetActivity()); @@ -97,10 +74,21 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Initialize the Messaging library"); ::firebase::ModuleInitializer initializer; - initializer.Initialize(app, nullptr, [](::firebase::App* app, void*) { - LogMessage("Try to initialize Firebase Messaging"); - return ::firebase::messaging::Initialize(*app, &g_listener); - }); + initializer.Initialize( + app, &listener, [](::firebase::App* app, void* userdata) { + LogMessage("Try to initialize Firebase Messaging"); + ::firebase::messaging::PollableListener* listener = + static_cast<::firebase::messaging::PollableListener*>(userdata); + firebase::messaging::MessagingOptions options; + // Prevent the app from requesting permission to show notifications + // immediately upon starting up. Since it the prompt is being + // suppressed, we must manually display it with a call to + // RequestPermission() elsewhere. + options.suppress_notification_permission_prompt = true; + + return ::firebase::messaging::Initialize(*app, listener, options); + }); + while (initializer.InitializeLastResult().status() != firebase::kFutureStatusComplete) { if (ProcessEvents(100)) return 1; // exit if requested @@ -114,14 +102,92 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Initialized Firebase Cloud Messaging."); - ::firebase::messaging::Subscribe("TestTopic"); - LogMessage("Subscribed to TestTopic"); + // This will display the prompt to request permission to receive notifications + // if the prompt has not already been displayed before. (If the user already + // responded to the prompt, their decision is cached by the OS and can be + // changed in the OS settings). + ::firebase::Future result = ::firebase::messaging::RequestPermission(); + LogMessage("Display permission prompt if necessary."); + while (result.status() == ::firebase::kFutureStatusPending) { + ProcessEvents(100); + } + if (result.error() == + ::firebase::messaging::kErrorFailedToRegisterForRemoteNotifications) { + LogMessage("Error registering for remote notifications."); + } else { + LogMessage("Finished checking for permission."); + } + + // Subscribe to topics. + WaitForFuture(::firebase::messaging::Subscribe("TestTopic"), + "::firebase::messaging::Subscribe(\"TestTopic\")", + ::firebase::messaging::kErrorNone); + WaitForFuture(::firebase::messaging::Subscribe("!@#$%^&*()"), + "::firebase::messaging::Subscribe(\"!@#$%^&*()\")", + ::firebase::messaging::kErrorInvalidTopicName); bool done = false; while (!done) { + std::string token; + if (listener.PollRegistrationToken(&token)) { + LogMessage("Received Registration Token: %s", token.c_str()); + } + + ::firebase::messaging::Message message; + while (listener.PollMessage(&message)) { + LogMessage("Received a new message"); + LogMessage("This message was %s by the user", + message.notification_opened ? "opened" : "not opened"); + if (!message.from.empty()) LogMessage("from: %s", message.from.c_str()); + if (!message.error.empty()) + LogMessage("error: %s", message.error.c_str()); + if (!message.message_id.empty()) { + LogMessage("message_id: %s", message.message_id.c_str()); + } + if (!message.link.empty()) { + LogMessage(" link: %s", message.link.c_str()); + } + if (!message.data.empty()) { + LogMessage("data:"); + for (const auto& field : message.data) { + LogMessage(" %s: %s", field.first.c_str(), field.second.c_str()); + } + } + if (message.notification) { + LogMessage("notification:"); + if (message.notification->android) { + LogMessage(" android:"); + LogMessage(" channel_id: %s", + message.notification->android->channel_id.c_str()); + } + if (!message.notification->title.empty()) { + LogMessage(" title: %s", message.notification->title.c_str()); + } + if (!message.notification->body.empty()) { + LogMessage(" body: %s", message.notification->body.c_str()); + } + if (!message.notification->icon.empty()) { + LogMessage(" icon: %s", message.notification->icon.c_str()); + } + if (!message.notification->tag.empty()) { + LogMessage(" tag: %s", message.notification->tag.c_str()); + } + if (!message.notification->color.empty()) { + LogMessage(" color: %s", message.notification->color.c_str()); + } + if (!message.notification->sound.empty()) { + LogMessage(" sound: %s", message.notification->sound.c_str()); + } + if (!message.notification->click_action.empty()) { + LogMessage(" click_action: %s", + message.notification->click_action.c_str()); + } + } + } // Process events so that the client doesn't hang. done = ProcessEvents(1000); } + ::firebase::messaging::Terminate(); delete app; diff --git a/messaging/testapp/src/desktop/desktop_main.cc b/messaging/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/messaging/testapp/src/desktop/desktop_main.cc +++ b/messaging/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/messaging/testapp/testapp.xcodeproj/project.pbxproj b/messaging/testapp/testapp.xcodeproj/project.pbxproj index da19cde8..c5f566b4 100644 --- a/messaging/testapp/testapp.xcodeproj/project.pbxproj +++ b/messaging/testapp/testapp.xcodeproj/project.pbxproj @@ -212,7 +212,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -249,7 +249,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/messaging/testapp/testapp/Info.plist b/messaging/testapp/testapp/Info.plist index 2fedb79c..2faa0b0d 100644 --- a/messaging/testapp/testapp/Info.plist +++ b/messaging/testapp/testapp/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.google.ios.messaging.testapp + com.google.FirebaseCppMessagingTestApp.dev CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -16,8 +16,6 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? UIBackgroundModes remote-notification @@ -31,7 +29,7 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID diff --git a/readme.md b/readme.md index 5c7e288c..53fededc 100644 --- a/readme.md +++ b/readme.md @@ -6,6 +6,13 @@ iOS and Android samples for the For more information, see [firebase.google.com](https://firebase.google.com). +## Issue reporting + +This repo no longer accepts issue reporting. For Firebase C++ SDK related +issues, please report to +[Firebase C++ Open-source](https://github.com/firebase/firebase-cpp-sdk/issues). +Please fill in the template to expedite the support. + ## How to make contributions? Please read and follow the steps in [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/remote_config/testapp/AndroidManifest.xml b/remote_config/testapp/AndroidManifest.xml index ef0b8708..a7a904df 100644 --- a/remote_config/testapp/AndroidManifest.xml +++ b/remote_config/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + ) + 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_remote_config firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/remote_config/testapp/Podfile b/remote_config/testapp/Podfile index 6e80e7af..14a96f7d 100644 --- a/remote_config/testapp/Podfile +++ b/remote_config/testapp/Podfile @@ -1,6 +1,7 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '13.0' +use_frameworks! # Firebase Remote Config test application. target 'testapp' do - pod 'Firebase/RemoteConfig' + pod 'Firebase/RemoteConfig', '10.25.0' end diff --git a/remote_config/testapp/build.gradle b/remote_config/testapp/build.gradle index ba29b197..4802ecbf 100644 --- a/remote_config/testapp/build.gradle +++ b/remote_config/testapp/build.gradle @@ -2,117 +2,75 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.android.remoteconfig.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.android.remoteconfig.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/remote_config.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile 'com.google.firebase:firebase-config:10.2.0' - compile 'com.google.android.gms:play-services-base:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + remoteConfig } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/remote_config/testapp/gradle.properties b/remote_config/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/remote_config/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/remote_config/testapp/gradle/wrapper/gradle-wrapper.properties b/remote_config/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/remote_config/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/remote_config/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/remote_config/testapp/jni/Android.mk b/remote_config/testapp/jni/Android.mk deleted file mode 100644 index 6a8ff00a..00000000 --- a/remote_config/testapp/jni/Android.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_remote_config -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libremote_config.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_remote_config \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/remote_config/testapp/jni/Application.mk b/remote_config/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/remote_config/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/remote_config/testapp/proguard.pro b/remote_config/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/remote_config/testapp/proguard.pro +++ b/remote_config/testapp/proguard.pro @@ -1 +1,2 @@ +-ignorewarnings -keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/remote_config/testapp/readme.md b/remote_config/testapp/readme.md index e0c0385b..81242895 100644 --- a/remote_config/testapp/readme.md +++ b/remote_config/testapp/readme.md @@ -18,16 +18,16 @@ Building and Running the testapp - Link your iOS app to the Firebase libraries. - Get CocoaPods version 1 or later by running, ``` - $ sudo gem install CocoaPods --pre + sudo gem install cocoapods --pre ``` - From the testapp directory, install the CocoaPods listed in the Podfile by running, ``` - $ pod install + pod install ``` - Open the generated Xcode workspace (which now has the CocoaPods), ``` - $ open testapp.xcworkspace + open testapp.xcworkspace ``` - For further details please refer to the [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). @@ -41,7 +41,7 @@ Building and Running the testapp console to the testapp root directory. This file identifies your iOS app to the Firebase backend. - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework @@ -89,18 +89,19 @@ Building and Running the testapp - For further details please refer to the [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Configure the location of the Firebase C++ SDK by setting the firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For example, in the project directory: ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties ``` - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. - Open *build.gradle* in Android Studio. - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. @@ -110,6 +111,49 @@ Building and Running the testapp in the logcat output of Android studio or by running "adb logcat" from the command line. +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. + Using the Test App ------------------ - In the Firebase Console, under "Remote Config", you can define parameters. @@ -127,7 +171,7 @@ keys that begin with "TestD". Support ------- -https://firebase.google.com/support/ +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -148,4 +192,3 @@ 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. - diff --git a/remote_config/testapp/settings.gradle b/remote_config/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/remote_config/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/remote_config/testapp/src/common_main.cc b/remote_config/testapp/src/common_main.cc index d392442d..3bc35e2c 100644 --- a/remote_config/testapp/src/common_main.cc +++ b/remote_config/testapp/src/common_main.cc @@ -19,32 +19,56 @@ #include "firebase/util.h" // Thin OS abstraction layer. -#include "main.h" // NOLINT +#include "main.h" // NOLINT + +using firebase::remote_config::RemoteConfig; + +// Convert remote_config::ValueSource to a string. +const char *ValueSourceToString(firebase::remote_config::ValueSource source) { + static const char *kSourceToString[] = { + "Static", // kValueSourceStaticValue + "Remote", // kValueSourceRemoteValue + "Default", // kValueSourceDefaultValue + }; + return kSourceToString[source]; +} + +RemoteConfig *rc_ = nullptr; // Execute all methods of the C++ Remote Config API. -extern "C" int common_main(int argc, const char* argv[]) { +extern "C" int common_main(int argc, const char *argv[]) { namespace remote_config = ::firebase::remote_config; - ::firebase::App* app; + ::firebase::App *app; + + // Initialization LogMessage("Initialize the Firebase Remote Config library"); #if defined(__ANDROID__) app = ::firebase::App::Create(GetJniEnv(), GetActivity()); #else app = ::firebase::App::Create(); -#endif // defined(__ANDROID__) +#endif // defined(__ANDROID__) LogMessage("Created the Firebase app %x", static_cast(reinterpret_cast(app))); ::firebase::ModuleInitializer initializer; - initializer.Initialize(app, nullptr, [](::firebase::App* app, void*) { - LogMessage("Try to initialize Remote Config"); - return ::firebase::remote_config::Initialize(*app); + + void *ptr = nullptr; + ptr = &rc_; + initializer.Initialize(app, ptr, [](::firebase::App *app, void *target) { + LogMessage("Try to initialize Firebase RemoteConfig"); + RemoteConfig **rc_ptr = reinterpret_cast(target); + *rc_ptr = RemoteConfig::GetInstance(app); + return firebase::kInitResultSuccess; }); + while (initializer.InitializeLastResult().status() != firebase::kFutureStatusComplete) { - if (ProcessEvents(100)) return 1; // exit if requested + if (ProcessEvents(100)) + return 1; // exit if requested } + if (initializer.InitializeLastResult().error() != 0) { LogMessage("Failed to initialize Firebase Remote Config: %s", initializer.InitializeLastResult().error_message()); @@ -54,6 +78,8 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Initialized the Firebase Remote Config API"); + // Initialization Complete + // Set Defaults, and test them static const unsigned char kBinaryDefaults[] = {6, 0, 0, 6, 7, 3}; static const remote_config::ConfigKeyValueVariant defaults[] = { @@ -62,48 +88,70 @@ extern "C" int common_main(int argc, const char* argv[]) { {"TestDouble", 3.14}, {"TestString", "Hello World"}, {"TestData", firebase::Variant::FromStaticBlob(kBinaryDefaults, - sizeof(kBinaryDefaults))}}; + sizeof(kBinaryDefaults))}, + {"TestDefaultOnly", "Default value that won't be overridden"}}; size_t default_count = sizeof(defaults) / sizeof(defaults[0]); - remote_config::SetDefaults(defaults, default_count); + rc_->SetDefaults(defaults, default_count); // The return values may not be the set defaults, if a fetch was previously // completed for the app that set them. + remote_config::ValueInfo value_info; { - bool result = remote_config::GetBoolean("TestBoolean"); - LogMessage("Get TestBoolean %d", result ? 1 : 0); + bool result = rc_->GetBoolean("TestBoolean", &value_info); + LogMessage("Get TestBoolean %d %s", result ? 1 : 0, + ValueSourceToString(value_info.source)); } { - int64_t result = remote_config::GetLong("TestLong"); - LogMessage("Get TestLong %lld", result); + int64_t result = rc_->GetLong("TestLong", &value_info); + LogMessage("Get TestLong %lld %s", result, + ValueSourceToString(value_info.source)); } { - double result = remote_config::GetDouble("TestDouble"); - LogMessage("Get TestDouble %f", result); + double result = rc_->GetDouble("TestDouble", &value_info); + LogMessage("Get TestDouble %f %s", result, + ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestString"); - LogMessage("Get TestString %s", result.c_str()); + std::string result = rc_->GetString("TestString", &value_info); + LogMessage("Get TestString \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); } { - std::vector result = remote_config::GetData("TestData"); + std::vector result = rc_->GetData("TestData"); for (size_t i = 0; i < result.size(); ++i) { const unsigned char value = result[i]; LogMessage("TestData[%d] = 0x%02x", i, value); } } + { + std::string result = rc_->GetString("TestDefaultOnly", &value_info); + LogMessage("Get TestDefaultOnly \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); + } + { + std::string result = rc_->GetString("TestNotSet", &value_info); + LogMessage("Get TestNotSet \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); + } - // Enable developer mode and verified it's enabled. - // NOTE: Developer mode should not be enabled in production applications. - remote_config::SetConfigSetting(remote_config::kConfigSettingDeveloperMode, - "1"); - if ((*remote_config::GetConfigSetting( - remote_config::kConfigSettingDeveloperMode) - .c_str()) != '1') { - LogMessage("Failed to enable developer mode"); + // Test the existence of the keys by name. + { + // Print out the keys with default values. + std::vector keys = rc_->GetKeys(); + LogMessage("GetKeys:"); + for (auto s = keys.begin(); s != keys.end(); ++s) { + LogMessage(" %s", s->c_str()); + } + keys = rc_->GetKeysByPrefix("TestD"); + LogMessage("GetKeysByPrefix(\"TestD\"):"); + for (auto s = keys.begin(); s != keys.end(); ++s) { + LogMessage(" %s", s->c_str()); + } } + // Test Fetch... LogMessage("Fetch..."); - auto future_result = remote_config::Fetch(0); + auto future_result = rc_->Fetch(0); while (future_result.status() == firebase::kFutureStatusPending) { if (ProcessEvents(1000)) { break; @@ -112,50 +160,78 @@ extern "C" int common_main(int argc, const char* argv[]) { if (future_result.status() == firebase::kFutureStatusComplete) { LogMessage("Fetch Complete"); - bool activate_result = remote_config::ActivateFetched(); - LogMessage("ActivateFetched %s", activate_result ? "succeeded" : "failed"); + auto activate_future_result = rc_->Activate(); + while (future_result.status() == firebase::kFutureStatusPending) { + if (ProcessEvents(1000)) { + break; + } + } - const remote_config::ConfigInfo& info = remote_config::GetInfo(); - LogMessage("Info last_fetch_time_ms=%d fetch_status=%d failure_reason=%d", - static_cast(info.fetch_time), info.last_fetch_status, - info.last_fetch_failure_reason); + bool activate_result = activate_future_result.result(); + LogMessage("Activate %s", activate_result ? "succeeded" : "failed"); + + const remote_config::ConfigInfo &info = rc_->GetInfo(); + LogMessage("Info last_fetch_time_ms=%d (year=%.2f) fetch_status=%d " + "failure_reason=%d throttled_end_time=%d", + static_cast(info.fetch_time), + 1970.0f + static_cast(info.fetch_time) / + (1000.0f * 60.0f * 60.0f * 24.0f * 365.0f), + info.last_fetch_status, info.last_fetch_failure_reason, + info.throttled_end_time); // Print out the new values, which may be updated from the Fetch. { - bool result = remote_config::GetBoolean("TestBoolean"); - LogMessage("Updated TestBoolean %d", result ? 1 : 0); + bool result = rc_->GetBoolean("TestBoolean", &value_info); + LogMessage("Updated TestBoolean %d %s", result ? 1 : 0, + ValueSourceToString(value_info.source)); } { - int64_t result = remote_config::GetLong("TestLong"); - LogMessage("Updated TestLong %lld", result); + int64_t result = rc_->GetLong("TestLong", &value_info); + LogMessage("Updated TestLong %lld %s", result, + ValueSourceToString(value_info.source)); } { - double result = remote_config::GetDouble("TestDouble"); - LogMessage("Updated TestDouble %f", result); + double result = rc_->GetDouble("TestDouble", &value_info); + LogMessage("Updated TestDouble %f %s", result, + ValueSourceToString(value_info.source)); } { - std::string result = remote_config::GetString("TestString"); - LogMessage("Updated TestString %s", result.c_str()); + std::string result = rc_->GetString("TestString", &value_info); + LogMessage("Updated TestString \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); } { - std::vector result = remote_config::GetData("TestData"); + std::vector result = rc_->GetData("TestData"); for (size_t i = 0; i < result.size(); ++i) { const unsigned char value = result[i]; LogMessage("TestData[%d] = 0x%02x", i, value); } } - - // Print out the keys that are now tied to data - std::vector keys = remote_config::GetKeys(); - LogMessage("GetKeys:"); - for (auto s = keys.begin(); s != keys.end(); ++s) { - LogMessage(" %s", s->c_str()); + { + std::string result = rc_->GetString("TestDefaultOnly", &value_info); + LogMessage("Get TestDefaultOnly \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); } - keys = remote_config::GetKeysByPrefix("TestD"); - LogMessage("GetKeysByPrefix(\"TestD\"):"); - for (auto s = keys.begin(); s != keys.end(); ++s) { - LogMessage(" %s", s->c_str()); + { + std::string result = rc_->GetString("TestNotSet", &value_info); + LogMessage("Get TestNotSet \"%s\" %s", result.c_str(), + ValueSourceToString(value_info.source)); + } + { + // Print out the keys that are now tied to data + std::vector keys = rc_->GetKeys(); + LogMessage("GetKeys:"); + for (auto s = keys.begin(); s != keys.end(); ++s) { + LogMessage(" %s", s->c_str()); + } + keys = rc_->GetKeysByPrefix("TestD"); + LogMessage("GetKeysByPrefix(\"TestD\"):"); + for (auto s = keys.begin(); s != keys.end(); ++s) { + LogMessage(" %s", s->c_str()); + } } + } else { + LogMessage("Fetch Incomplete"); } // Release a handle to the future so we can shutdown the Remote Config API // when exiting the app. Alternatively we could have placed future_result @@ -166,7 +242,8 @@ extern "C" int common_main(int argc, const char* argv[]) { while (!ProcessEvents(1000)) { } - remote_config::Terminate(); + delete rc_; + rc_ = nullptr; delete app; return 0; diff --git a/remote_config/testapp/src/desktop/desktop_main.cc b/remote_config/testapp/src/desktop/desktop_main.cc index 99ace543..0220c688 100644 --- a/remote_config/testapp/src/desktop/desktop_main.cc +++ b/remote_config/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,35 @@ #include #include #include -#ifndef _WIN32 + +#ifdef _WIN32 +#include +#define chdir _chdir +#else #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -50,6 +69,10 @@ bool ProcessEvents(int msec) { return quit; } +std::string PathForResource() { + return std::string(); +} + void LogMessage(const char* format, ...) { va_list list; va_start(list, format); @@ -61,7 +84,22 @@ void LogMessage(const char* format, ...) { WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + int main(int argc, const char* argv[]) { + ChangeToFileDirectory( + FIREBASE_CONFIG_STRING[0] != '\0' ? + FIREBASE_CONFIG_STRING : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else @@ -69,3 +107,19 @@ int main(int argc, const char* argv[]) { #endif // _WIN32 return common_main(argc, argv); } + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif diff --git a/remote_config/testapp/testapp.xcodeproj/project.pbxproj b/remote_config/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/remote_config/testapp/testapp.xcodeproj/project.pbxproj +++ b/remote_config/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/remote_config/testapp/testapp/Info.plist b/remote_config/testapp/testapp/Info.plist index 01887a39..907e8a65 100644 --- a/remote_config/testapp/testapp/Info.plist +++ b/remote_config/testapp/testapp/Info.plist @@ -16,8 +16,6 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleURLTypes @@ -27,7 +25,7 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID diff --git a/storage/testapp/AndroidManifest.xml b/storage/testapp/AndroidManifest.xml index 3a4a60fd..253ce8c0 100644 --- a/storage/testapp/AndroidManifest.xml +++ b/storage/testapp/AndroidManifest.xml @@ -6,9 +6,10 @@ - + - + + + + android:value="android_main_automated" /> diff --git a/storage/testapp/CMakeLists.txt b/storage/testapp/CMakeLists.txt new file mode 100644 index 00000000..f885aa89 --- /dev/null +++ b/storage/testapp/CMakeLists.txt @@ -0,0 +1,125 @@ +cmake_minimum_required(VERSION 2.8) + +# User settings for Firebase samples. +# 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() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +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() + +# 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) + +# Sample source files. +set(FIREBASE_SAMPLE_COMMON_SRCS + src/main.h + src/common_main.cc +) + +# The include directory for the testapp. +include_directories(src) + +# Sample uses some features that require C++ 11, such as lambdas. +set (CMAKE_CXX_STANDARD 11) + +if(ANDROID) + # Build an Android application. + + # Source files used for the Android build. + set(FIREBASE_SAMPLE_ANDROID_SRCS + src/android/android_main.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") + + # Define the target as a shared library, as that is what gradle expects. + set(target_name "android_main") + add_library(${target_name} SHARED + ${FIREBASE_SAMPLE_ANDROID_SRCS} + ${FIREBASE_SAMPLE_COMMON_SRCS} + ) + + target_link_libraries(${target_name} + log android atomic native_app_glue + ) + + target_include_directories(${target_name} PRIVATE + ${ANDROID_NDK}/sources/android/native_app_glue) + + set(ADDITIONAL_LIBS) +else() + # Build a desktop application. + + # 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 sample. + set(FIREBASE_SAMPLE_DESKTOP_SRCS + src/desktop/desktop_main.cc + ) + + set(target_name "desktop_testapp") + add_executable(${target_name} + ${FIREBASE_SAMPLE_DESKTOP_SRCS} + ${FIREBASE_SAMPLE_COMMON_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 ${config}) + add_custom_command( + TARGET ${target_name} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${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_storage firebase_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/storage/testapp/Podfile b/storage/testapp/Podfile index 4946e0be..7010e7c0 100644 --- a/storage/testapp/Podfile +++ b/storage/testapp/Podfile @@ -1,7 +1,8 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' -# Firebase Storage test application. +platform :ios, '13.0' +use_frameworks! +# Cloud Storage for Firebase test application. target 'testapp' do -pod 'Firebase/Storage' -pod 'Firebase/Auth' + pod 'Firebase/Storage', '10.25.0' + pod 'Firebase/Auth', '10.25.0' end diff --git a/storage/testapp/build.gradle b/storage/testapp/build.gradle index 527f53cc..aa20418d 100644 --- a/storage/testapp/build.gradle +++ b/storage/testapp/build.gradle @@ -2,118 +2,76 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - classpath 'com.google.gms:google-services:3.0.0' + classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.google.gms:google-services:4.0.1' } } allprojects { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } } apply plugin: 'com.android.application' -// Pre-experimental Gradle plug-in NDK boilerplate below. -// Right now the Firebase plug-in does not work with the experimental -// Gradle plug-in so we're using ndk-build for the moment. -project.ext { - // Configure the Firebase C++ SDK location. - 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 ((new 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)) - } - // Check the NDK location using the same configuration options as the - // experimental Gradle plug-in. - ndk_dir = project.android.ndkDirectory - if (ndk_dir == null || !ndk_dir.exists()) { - ndk_dir = System.getenv('ANDROID_NDK_HOME') - if (ndk_dir == null || ndk_dir.isEmpty()) { - throw new StopActionException( - 'Android NDK directory should be specified using the ndk.dir ' + - 'property or ANDROID_NDK_HOME environment variable.') - } - } -} - android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + compileSdkVersion 34 + ndkPath System.getenv('ANDROID_NDK_HOME') + buildToolsVersion '30.0.2' - sourceSets { - main { - jniLibs.srcDirs = ['libs'] - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src/android/java'] - res.srcDirs = ['res'] - } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src/android/java'] + res.srcDirs = ['res'] } + } - defaultConfig { - applicationId 'com.google.firebase.cpp.storage.testapp' - minSdkVersion 14 - targetSdkVersion 23 - versionCode 1 - versionName '1.0' + defaultConfig { + applicationId 'com.google.firebase.cpp.storage.testapp' + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName '1.0' + externalNativeBuild.cmake { + arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir" } - buildTypes { - release { - minifyEnabled true - proguardFile getDefaultProguardFile('proguard-android.txt') - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/auth.pro") - proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/storage.pro") - proguardFile file('proguard.pro') - } + } + externalNativeBuild.cmake { + path 'CMakeLists.txt' + } + buildTypes { + release { + minifyEnabled true + proguardFile getDefaultProguardFile('proguard-android.txt') + proguardFile file('proguard.pro') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile 'com.google.firebase:firebase-auth:10.2.0' - compile 'com.google.firebase:firebase-storage:10.2.0' +apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle" +firebaseCpp.dependencies { + auth + storage } apply plugin: 'com.google.gms.google-services' - -task ndkBuildCompile(type:Exec) { - description 'Use ndk-build to compile the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - sprintf("APP_PLATFORM=android-%d", - android.defaultConfig.minSdkVersion.mApiLevel)) -} - -task ndkBuildClean(type:Exec) { - description 'Use ndk-build to clean the C++ application.' - commandLine("${project.ext.ndk_dir}${File.separator}ndk-build", - "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}", - "clean") -} - -// Once the Android Gradle plug-in has generated tasks, add dependencies for -// the ndk-build targets. -project.afterEvaluate { - preBuild.dependsOn(ndkBuildCompile) - clean.dependsOn(ndkBuildClean) -} diff --git a/storage/testapp/gradle.properties b/storage/testapp/gradle.properties new file mode 100644 index 00000000..d7ba8f42 --- /dev/null +++ b/storage/testapp/gradle.properties @@ -0,0 +1 @@ +android.useAndroidX = true diff --git a/storage/testapp/gradle/wrapper/gradle-wrapper.properties b/storage/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/storage/testapp/gradle/wrapper/gradle-wrapper.properties +++ b/storage/testapp/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Apr 10 15:27:10 PDT 2013 +#Mon Nov 27 14:03:45 PST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip diff --git a/storage/testapp/jni/Android.mk b/storage/testapp/jni/Android.mk deleted file mode 100644 index 847a7783..00000000 --- a/storage/testapp/jni/Android.mk +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -LOCAL_PATH:=$(call my-dir)/.. - -ifeq ($(FIREBASE_CPP_SDK_DIR),) -$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.) -endif - -# With Firebase libraries for the selected build configuration (ABI + STL) -STL:=$(firstword $(subst _, ,$(APP_STL))) -FIREBASE_LIBRARY_PATH:=\ -$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_app -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_auth -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libauth.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=firebase_storage -LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libstorage.a -LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include -include $(PREBUILT_STATIC_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE:=android_main -LOCAL_SRC_FILES:=\ - $(LOCAL_PATH)/src/common_main.cc \ - $(LOCAL_PATH)/src/android/android_main.cc -LOCAL_STATIC_LIBRARIES:=\ - firebase_storage \ - firebase_auth \ - firebase_app -LOCAL_WHOLE_STATIC_LIBRARIES:=\ - android_native_app_glue -LOCAL_C_INCLUDES:=\ - $(NDK_ROOT)/sources/android/native_app_glue \ - $(LOCAL_PATH)/src -LOCAL_LDLIBS:=-llog -landroid -latomic -LOCAL_ARM_MODE:=arm -LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined -include $(BUILD_SHARED_LIBRARY) - -$(call import-add-path,$(NDK_ROOT)/sources/android) -$(call import-module,android/native_app_glue) diff --git a/storage/testapp/jni/Application.mk b/storage/testapp/jni/Application.mk deleted file mode 100644 index 53ed56a2..00000000 --- a/storage/testapp/jni/Application.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2016 Google Inc. 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. - -APP_PLATFORM:=android-14 -NDK_TOOLCHAIN_VERSION=clang -APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64 -APP_STL:=c++_static -APP_MODULES:=android_main -APP_CPPFLAGS+=-std=c++11 diff --git a/storage/testapp/proguard.pro b/storage/testapp/proguard.pro index c7e9278d..2d04b8a9 100644 --- a/storage/testapp/proguard.pro +++ b/storage/testapp/proguard.pro @@ -1 +1,2 @@ --keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/storage/testapp/readme.md b/storage/testapp/readme.md index 5e26d7d7..96cdcaae 100644 --- a/storage/testapp/readme.md +++ b/storage/testapp/readme.md @@ -1,17 +1,18 @@ -Firebase Storage Quickstart +Cloud Storage for Firebase Quickstart ======================== -The Firebase Storage Test Application (testapp) demonstrates Firebase Storage -operations with the Firebase Storage C++ SDK. The application has no user -interface and simply logs actions it's performing to the console. +The Cloud Storage for Firebase Test Application (testapp) demonstrates +Cloud Storage operations with the Firebase C++ SDK for Cloud Storage. +The application has no user interface and simply logs actions it's performing +to the console. The testapp performs the following: - Creates a firebase::App in a platform-specific way. The App holds platform-specific context that's used by other Firebase APIs, and is a - central point for communication between the Firebase Storage C++ and + central point for communication between the Cloud Storage C++ and Firebase Auth C++ libraries. - Gets a pointer to firebase::Auth, and signs in anonymously. This allows the - testapp to access a Firebase Storage instance with authentication rules + testapp to access a Cloud Storage instance with authentication rules enabled, which is the default setting in Firebase Console. - Gets a StorageReference to the root node's "test_app_data" child, uses StorageReference::Child() to create a child with a unique key based on the @@ -23,12 +24,12 @@ The testapp performs the following: return the expected values for things like size and date modified. - Disconnects and then reconnects and verifies it still has access to the files uploaded. - - Shuts down the Firebase Storage, Firebase Auth, and Firebase App systems. + - Shuts down the Cloud Storage, Firebase Auth, and Firebase App systems. Introduction ------------ -- [Read more about Firebase Storage](https://firebase.google.com/docs/storage/) +- [Read more about Cloud Storage for Firebase](https://firebase.google.com/docs/storage/) Building and Running the testapp -------------------------------- @@ -37,16 +38,16 @@ Building and Running the testapp - Link your iOS app to the Firebase libraries. - Get CocoaPods version 1 or later by running, ``` - $ sudo gem install CocoaPods --pre + sudo gem install cocoapods --pre ``` - From the testapp directory, install the CocoaPods listed in the Podfile by running, ``` - $ pod install + pod install ``` - Open the generated Xcode workspace (which now has the CocoaPods), ``` - $ open testapp.xcworkspace + open testapp.xcworkspace ``` - For further details please refer to the [general instructions for setting up an iOS app with Firebase](https://firebase.google.com/docs/ios/setup). @@ -60,11 +61,11 @@ Building and Running the testapp to the Firebase backend. - In the Firebase console for your app, select "Auth", then enable "Anonymous". This will allow the testapp to use anonymous sign-in to - authenticate with Firebase Storage, which requires a signed-in user by + authenticate with Cloud Storage, which requires a signed-in user by default (an anonymous user will suffice). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Add the following frameworks from the Firebase C++ SDK to the project: - frameworks/ios/universal/firebase.framework - frameworks/ios/universal/firebase_auth.framework @@ -113,23 +114,24 @@ Building and Running the testapp Android app to the Firebase backend. - In the Firebase console for your app, select "Auth", then enable "Anonymous". This will allow the testapp to use anonymous sign-in to - authenticate with Firebase Storage, which requires a signed-in user by + authenticate with Cloud Storage, which requires a signed-in user by default (an anonymous user will suffice). - For further details please refer to the [general instructions for setting up an Android app with Firebase](https://firebase.google.com/docs/android/setup). - Download the Firebase C++ SDK linked from - https://firebase.google.com/docs/cpp/setup and unzip it to a - directory of your choice. + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. - Configure the location of the Firebase C++ SDK by setting the firebase\_cpp\_sdk.dir Gradle property to the SDK install directory. For example, in the project directory: ``` - > echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties + echo "systemProp.firebase\_cpp\_sdk.dir=/User/$USER/firebase\_cpp\_sdk" >> gradle.properties ``` - Ensure the Android SDK and NDK locations are set in Android Studio. - - From the Android Studio launch menu, go to - Configure/Project Defaults/Project Structure and download the SDK and NDK if - the locations are not yet set. + - From the Android Studio launch menu, go to `File/Project Structure...` or + `Configure/Project Defaults/Project Structure...` + (Shortcut: Control + Alt + Shift + S on windows, Command + ";" on a mac) + and download the SDK and NDK if the locations are not yet set. - Open *build.gradle* in Android Studio. - From the Android Studio launch menu, "Open an existing Android Studio project", and select `build.gradle`. @@ -139,10 +141,53 @@ Building and Running the testapp viewed on the device's display, or in the logcat output of Android studio or by running "adb logcat *:W android_main firebase" from the command line. +### Desktop + - Register your app with Firebase. + - Create a new app on the [Firebase console](https://firebase.google.com/console/), + following the above instructions for Android or iOS. + - If you have an Android project, add the `google-services.json` file that + you downloaded from the Firebase console to the root directory of the + testapp. + - If you have an iOS project, and don't wish to use an Android project, + you can use the Python script `generate_xml_from_google_services_json.py --plist`, + located in the Firebase C++ SDK, to convert your `GoogleService-Info.plist` + file into a `google-services-desktop.json` file, which can then be + placed in the root directory of the testapp. + - Download the Firebase C++ SDK linked from + [https://firebase.google.com/docs/cpp/setup](https://firebase.google.com/docs/cpp/setup) + and unzip it to a directory of your choice. + - Configure the testapp with the location of the Firebase C++ SDK. + This can be done a couple different ways (in highest to lowest priority): + - When invoking cmake, pass in the location with + -DFIREBASE_CPP_SDK_DIR=/path/to/firebase_cpp_sdk. + - Set an environment variable for FIREBASE_CPP_SDK_DIR to the path to use. + - Edit the CMakeLists.txt file, changing the FIREBASE_CPP_SDK_DIR path + to the appropriate location. + - From the testapp directory, generate the build files by running, + ``` + cmake . + ``` + If you want to use XCode, you can use -G"Xcode" to generate the project. + Similarly, to use Visual Studio, -G"Visual Studio 15 2017". For more + information, see + [CMake generators](https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html). + - Build the testapp, by either opening the generated project file based on + the platform, or running, + ``` + cmake --build . + ``` + - Execute the testapp by running, + ``` + ./desktop_testapp + ``` + Note that the executable might be under another directory, such as Debug. + - The testapp has no user interface, but the output can be viewed via the + console. + Support ------- -https://firebase.google.com/support/ +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -163,4 +208,3 @@ 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. - diff --git a/storage/testapp/res/values/strings.xml b/storage/testapp/res/values/strings.xml index 43d4a298..eec5f22d 100644 --- a/storage/testapp/res/values/strings.xml +++ b/storage/testapp/res/values/strings.xml @@ -1,4 +1,4 @@ - Firebase Storage Test + Cloud Storage for Firebase Test diff --git a/storage/testapp/settings.gradle b/storage/testapp/settings.gradle new file mode 100644 index 00000000..2a543b93 --- /dev/null +++ b/storage/testapp/settings.gradle @@ -0,0 +1,36 @@ +// Copyright 2018 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 ((new 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" \ No newline at end of file diff --git a/storage/testapp/src/android/android_main.cc b/storage/testapp/src/android/android_main.cc index 4b86ce30..d8e4d4dc 100644 --- a/storage/testapp/src/android/android_main.cc +++ b/storage/testapp/src/android/android_main.cc @@ -12,13 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include - #include #include #include +#include + #include +#include +#include +#include +#include +#include #include "main.h" // NOLINT @@ -37,6 +41,8 @@ static void OnAppCmd(struct android_app* app, int32_t cmd) { g_destroy_requested |= cmd == APP_CMD_DESTROY; } +namespace app_framework { + // Process events pending on the main thread. // Returns true when the app receives an event requesting exit. bool ProcessEvents(int msec) { @@ -103,7 +109,8 @@ class LoggingUtilsData { LoggingUtilsData() : logging_utils_class_(nullptr), logging_utils_add_log_text_(0), - logging_utils_init_log_window_(0) {} + logging_utils_init_log_window_(0), + logging_utils_get_did_touch_(0) {} ~LoggingUtilsData() { JNIEnv* env = GetJniEnv(); @@ -131,6 +138,8 @@ class LoggingUtilsData { logging_utils_class_, "initLogWindow", "(Landroid/app/Activity;)V"); logging_utils_add_log_text_ = env->GetStaticMethodID( logging_utils_class_, "addLogText", "(Ljava/lang/String;)V"); + logging_utils_get_did_touch_ = + env->GetStaticMethodID(logging_utils_class_, "getDidTouch", "()Z"); env->CallStaticVoidMethod(logging_utils_class_, logging_utils_init_log_window_, GetActivity()); @@ -146,10 +155,19 @@ class LoggingUtilsData { env->DeleteLocalRef(text_string); } + bool DidTouch() { + if (logging_utils_class_ == 0) return false; // haven't been initted yet + JNIEnv* env = GetJniEnv(); + assert(env); + return env->CallStaticBooleanMethod(logging_utils_class_, + logging_utils_get_did_touch_); + } + private: jclass logging_utils_class_; jmethodID logging_utils_add_log_text_; jmethodID logging_utils_init_log_window_; + jmethodID logging_utils_get_did_touch_; }; LoggingUtilsData* g_logging_utils_data; @@ -170,12 +188,10 @@ void CheckJNIException() { const char* exception_text = env->GetStringUTFChars(s, nullptr); // Log the exception text. - __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "-------------------JNI exception:"); - __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, "%s", - exception_text); - __android_log_print(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, - "-------------------"); + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "%s", exception_text); + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "-------------------"); // Also, assert fail. assert(false); @@ -187,23 +203,33 @@ void CheckJNIException() { } } -// Log a message that can be viewed in "adb logcat". void LogMessage(const char* format, ...) { - static const int kLineBufferSize = 100; - char buffer[kLineBufferSize + 2]; - va_list list; va_start(list, format); + LogMessageV(format, list); + va_end(list); +} + +// Log a message that can be viewed in "adb logcat". +void LogMessageV(const char* format, va_list list) { + static const int kLineBufferSize = 1024; + char buffer[kLineBufferSize + 2]; + int string_len = vsnprintf(buffer, kLineBufferSize, format, list); string_len = string_len < kLineBufferSize ? string_len : kLineBufferSize; // append a linebreak to the buffer: buffer[string_len] = '\n'; buffer[string_len + 1] = '\0'; - __android_log_vprint(ANDROID_LOG_INFO, FIREBASE_TESTAPP_NAME, format, list); - g_logging_utils_data->AppendText(buffer); + __android_log_vprint(ANDROID_LOG_INFO, TESTAPP_NAME, format, list); + fputs(buffer, stdout); + fflush(stdout); +} + +// Log a message that can be viewed in the console. +void AddToTextView(const char* str) { + app_framework::g_logging_utils_data->AppendText(str); CheckJNIException(); - va_end(list); } // Get the JNI environment. @@ -214,6 +240,75 @@ JNIEnv* GetJniEnv() { return result == JNI_OK ? env : nullptr; } +// Remove all lines starting with these strings. +static const char* const filter_lines[] = {"referenceTable ", nullptr}; + +bool should_filter(const char* str) { + for (int i = 0; filter_lines[i] != nullptr; ++i) { + if (strncmp(str, filter_lines[i], strlen(filter_lines[i])) == 0) + return true; + } + return false; +} + +void* stdout_logger(void* filedes_ptr) { + int fd = reinterpret_cast(filedes_ptr)[0]; + static std::string buffer; + char bufchar; + while (int n = read(fd, &bufchar, 1)) { + if (bufchar == '\0') { + break; + } + buffer = buffer + bufchar; + if (bufchar == '\n') { + if (!should_filter(buffer.c_str())) { + app_framework::AddToTextView(buffer.c_str()); + } + buffer.clear(); + } + } + JavaVM* jvm; + if (app_framework::GetJniEnv()->GetJavaVM(&jvm) == 0) { + jvm->DetachCurrentThread(); + } + return nullptr; +} + +struct FunctionData { + void* (*func)(void*); + void* data; +}; + +static void* CallFunction(void* bg_void) { + FunctionData* bg = reinterpret_cast(bg_void); + void* (*func)(void*) = bg->func; + void* data = bg->data; + // Clean up the data that was passed to us. + delete bg; + // Call the background function. + void* result = func(data); + // Then clean up the Java thread. + JavaVM* jvm; + if (app_framework::GetJniEnv()->GetJavaVM(&jvm) == 0) { + jvm->DetachCurrentThread(); + } + return result; +} + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + pthread_t thread; + // Rather than running pthread_create(func, data), we must package them into a + // struct, because the c++ thread needs to clean up the JNI thread after it + // finishes. + FunctionData* bg = new FunctionData; + bg->func = func; + bg->data = data; + pthread_create(&thread, nullptr, CallFunction, bg); + pthread_detach(thread); +} + +} // namespace app_framework + // Execute common_main(), flush pending events and finish the activity. extern "C" void android_main(struct android_app* state) { // native_app_glue spawns a new thread, calling android_main() when the @@ -238,18 +333,37 @@ extern "C" void android_main(struct android_app* state) { g_app_state->onAppCmd = OnAppCmd; // Create the logging display. - g_logging_utils_data = new LoggingUtilsData(); - g_logging_utils_data->Init(); + app_framework::g_logging_utils_data = new app_framework::LoggingUtilsData(); + app_framework::g_logging_utils_data->Init(); + + // Pipe stdout to AddToTextView so we get the gtest output. + int filedes[2]; + assert(pipe(filedes) != -1); + assert(dup2(filedes[1], STDOUT_FILENO) != -1); + pthread_t thread; + pthread_create(&thread, nullptr, app_framework::stdout_logger, + reinterpret_cast(filedes)); // Execute cross platform entry point. - static const char* argv[] = {FIREBASE_TESTAPP_NAME}; + static const char* argv[] = {TESTAPP_NAME}; int return_value = common_main(1, argv); (void)return_value; // Ignore the return value. - ProcessEvents(10); + + // Signal to stdout_logger to exit. + write(filedes[1], "\0", 1); + pthread_join(thread, nullptr); + close(filedes[0]); + close(filedes[1]); + // Pause a few seconds so you can see the results. If the user touches + // the screen during that time, don't exit until they choose to. + bool should_exit = false; + do { + should_exit = app_framework::ProcessEvents(10000); + } while (app_framework::g_logging_utils_data->DidTouch() && !should_exit); // Clean up logging display. - delete g_logging_utils_data; - g_logging_utils_data = nullptr; + delete app_framework::g_logging_utils_data; + app_framework::g_logging_utils_data = nullptr; // Finish the activity. if (!g_restarted) ANativeActivity_finish(state->activity); diff --git a/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index 11d67c5b..a49996a6 100644 --- a/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -15,8 +15,13 @@ package com.google.firebase.example; import android.app.Activity; +import android.graphics.Typeface; import android.os.Handler; import android.os.Looper; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.MotionEvent; +import android.view.View; import android.view.Window; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -27,29 +32,70 @@ * text to the screen, via a non-editable TextView. */ public class LoggingUtils { - public static TextView sTextView = null; + static TextView textView = null; + static ScrollView scrollView = null; + // Tracks if the log window was touched at least once since the testapp was started. + static boolean didTouch = false; public static void initLogWindow(Activity activity) { + initLogWindow(activity, true); + } + + public static void initLogWindow(Activity activity, boolean monospace) { LinearLayout linearLayout = new LinearLayout(activity); - ScrollView scrollView = new ScrollView(activity); - TextView textView = new TextView(activity); + scrollView = new ScrollView(activity); + textView = new TextView(activity); textView.setTag("Logger"); + if (monospace) { + textView.setTypeface(Typeface.MONOSPACE); + textView.setTextSize(10); + } linearLayout.addView(scrollView); scrollView.addView(textView); Window window = activity.getWindow(); window.takeSurface(null); window.setContentView(linearLayout); - sTextView = textView; + + // Force the TextView to stay scrolled to the bottom. + textView.addTextChangedListener( + new TextWatcher() { + @Override + public void afterTextChanged(Editable e) { + if (scrollView != null && !didTouch) { + // If the user never interacted with the screen, scroll to bottom. + scrollView.fullScroll(View.FOCUS_DOWN); + } + } + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int count, int after) {} + }); + textView.setOnTouchListener( + new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + didTouch = true; + return false; + } + }); } public static void addLogText(final String text) { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (sTextView != null) { - sTextView.append(text); - } - } - }); + new Handler(Looper.getMainLooper()) + .post( + new Runnable() { + @Override + public void run() { + if (textView != null) { + textView.append(text); + } + } + }); + } + + public static boolean getDidTouch() { + return didTouch; } } diff --git a/storage/testapp/src/common_main.cc b/storage/testapp/src/common_main.cc index b3abb779..a333c4fb 100644 --- a/storage/testapp/src/common_main.cc +++ b/storage/testapp/src/common_main.cc @@ -13,12 +13,13 @@ // limitations under the License. #include -#include + #include #include #include #include #include + #include "firebase/app.h" #include "firebase/auth.h" #include "firebase/future.h" @@ -29,32 +30,16 @@ // Thin OS abstraction layer. #include "main.h" // NOLINT +using app_framework::GetCurrentTimeInMicroseconds; +using app_framework::LogMessage; +using app_framework::ProcessEvents; + const char* kPutFileTestFile = "PutFileTest.txt"; const char* kGetFileTestFile = "GetFileTest.txt"; -class StorageListener : public firebase::storage::Listener { - public: - StorageListener() - : on_paused_was_called_(false), on_progress_was_called_(false) {} - - // Tracks whether OnPaused was ever called - void OnPaused(firebase::storage::Controller* controller) override { - (void)controller; - on_paused_was_called_ = true; - } - - void OnProgress(firebase::storage::Controller* controller) override { - (void)controller; - on_progress_was_called_ = true; - } - - bool on_paused_was_called() const { return on_paused_was_called_; } - bool on_progress_was_called() const { return on_progress_was_called_; } - - public: - bool on_paused_was_called_; - bool on_progress_was_called_; -}; +// Optionally set this to your Cloud Storage URL (gs://...) to test +// in a specific Cloud Storage bucket. +const char* kStorageUrl = nullptr; // Wait for a Future to be completed. If the Future returns an error, it will // be logged. @@ -74,14 +59,15 @@ extern "C" int common_main(int argc, const char* argv[]) { ::firebase::App* app; #if defined(__ANDROID__) - app = ::firebase::App::Create(GetJniEnv(), GetActivity()); + app = ::firebase::App::Create(app_framework::GetJniEnv(), + app_framework::GetActivity()); #else app = ::firebase::App::Create(); #endif // defined(__ANDROID__) LogMessage("Initialized Firebase App."); - LogMessage("Initialize Firebase Auth and Firebase Storage."); + LogMessage("Initialize Firebase Auth and Cloud Storage."); // Use ModuleInitializer to initialize both Auth and Storage, ensuring no // dependencies are missing. @@ -99,11 +85,15 @@ extern "C" int common_main(int argc, const char* argv[]) { return result; }, [](::firebase::App* app, void* data) { - LogMessage("Attempt to initialize Firebase Storage."); + LogMessage("Attempt to initialize Cloud Storage."); void** targets = reinterpret_cast(data); ::firebase::InitResult result; - *reinterpret_cast<::firebase::storage::Storage**>(targets[1]) = - ::firebase::storage::Storage::GetInstance(app, &result); + firebase::storage::Storage* storage = + firebase::storage::Storage::GetInstance(app, kStorageUrl, &result); + *reinterpret_cast<::firebase::storage::Storage**>(targets[1]) = storage; + LogMessage("Initialized storage with URL %s, %s", + kStorageUrl ? kStorageUrl : "(null)", + storage->url().c_str()); return result; }}; @@ -119,14 +109,14 @@ extern "C" int common_main(int argc, const char* argv[]) { ProcessEvents(2000); return 1; } - LogMessage("Successfully initialized Firebase Auth and Firebase Storage."); + LogMessage("Successfully initialized Firebase Auth and Cloud Storage."); // Sign in using Auth before accessing Storage. // The default Storage permissions allow anonymous users access. This will // work as long as your project's Authentication permissions allow anonymous // signin. { - firebase::Future sign_in_future = + firebase::Future sign_in_future = auth->SignInAnonymously(); WaitForCompletion(sign_in_future, "SignInAnonymously"); if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { @@ -138,25 +128,24 @@ extern "C" int common_main(int argc, const char* argv[]) { " Ensure your application has the Anonymous sign-in provider " "enabled in Firebase Console."); LogMessage( - " Attempting to connect to Firebase Storage anyway. This may fail " + " Attempting to connect to Cloud Storage anyway. This may fail " "depending on the security settings."); } } // Generate a folder for the test data based on the time in milliseconds. - struct timeval now; - gettimeofday(&now, nullptr); - int64_t time_in_microseconds = now.tv_sec * 1000000LL + now.tv_usec; + int64_t time_in_microseconds = GetCurrentTimeInMicroseconds(); + char buffer[21] = {0}; snprintf(buffer, sizeof(buffer), "%lld", static_cast(time_in_microseconds)); // NOLINT std::string saved_url = buffer; - // Create a unique child in the storage that we can run our tests in. - firebase::storage::StorageReference ref = - storage->GetReference("test_app_data").Child(saved_url); + // Create a unique child in the storage that we can run in. + firebase::storage::StorageReference ref; + ref = storage->GetReference("test_app_data").Child(saved_url); - LogMessage("Storage URL: gs://%s/%s", ref.bucket().c_str(), + LogMessage("Storage URL: gs://%s%s", ref.bucket().c_str(), ref.full_path().c_str()); // Read and write from memory. This will save a small file and then read it @@ -172,388 +161,50 @@ extern "C" int common_main(int argc, const char* argv[]) { "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " "culpa qui officia deserunt mollit anim id est laborum."; { - LogMessage("TEST: Write a sample file from byte buffer."); + LogMessage("Write a sample file."); + std::string custom_metadata_key = "specialkey"; + std::string custom_metadata_value = "secret value"; + firebase::storage::Metadata metadata; + metadata.set_content_type("test/plain"); + (*metadata.custom_metadata())[custom_metadata_key] = + custom_metadata_value; firebase::Future future = ref.Child("TestFile") - .Child("File1.txt") - .PutBytes(kSimpleTestFile.data(), kSimpleTestFile.size()); - WaitForCompletion(future, "Write Bytes"); - if (future.error() != firebase::storage::kErrorNone) { - LogMessage("ERROR: Write sample file failed."); - LogMessage(" File1.txt: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Wrote file with PutBytes."); - auto metadata = future.result(); - if (metadata->size_bytes() == kSimpleTestFile.size()) { - LogMessage("SUCCESS: Metadata reports correct size."); - } else { - LogMessage("ERROR: Metadata reports incorrect size."); - LogMessage(" Got %i bytes, expected %i bytes.", - metadata->size_bytes(), kSimpleTestFile.size()); - } - } - } - - { - LogMessage("TEST: Write a sample file from local file."); - - // Write file that we're going to upload. - std::string path = PathForResource() + kPutFileTestFile; - // Firebase Storage expects file:// in front of local paths. - std::string file_path = "file://" + path; - - FILE* file = fopen(path.c_str(), "w"); - std::fwrite(kSimpleTestFile.c_str(), 1, kSimpleTestFile.size(), file); - fclose(file); - - firebase::Future future = - ref.Child("TestFile").Child("File2.txt").PutFile(file_path.c_str()); - WaitForCompletion(future, "Write File"); - if (future.error() != firebase::storage::kErrorNone) { - LogMessage("ERROR: Write file failed."); - LogMessage(" File1.txt: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Wrote file with PutFile."); - auto metadata = future.result(); - if (metadata->size_bytes() == kSimpleTestFile.size()) { - LogMessage("SUCCESS: Metadata reports correct size."); - } else { - LogMessage("ERROR: Metadata reports incorrect size."); - LogMessage(" Got %i bytes, expected %i bytes.", - metadata->size_bytes(), kSimpleTestFile.size()); - } - } - } - - // Get the values that we just set, and confirm that they match what we - // set them to. - { - LogMessage("TEST: Read a sample file with GetBytes."); - - // Read the storage file to byte buffer. - { - const size_t kBufferSize = 1024; - char buffer[kBufferSize]; - - firebase::Future future = ref.Child("TestFile") - .Child("File1.txt") - .GetBytes(buffer, kBufferSize); - WaitForCompletion(future, "Read Bytes"); - - // Check if the file contents is correct. - if (future.error() == firebase::storage::kErrorNone) { - if (*future.result() != kSimpleTestFile.size()) { - LogMessage( - "ERROR: Read file failed, read incorrect number of bytes (read " - "%z, expected %z)", - *future.result(), kSimpleTestFile.size()); - } else if (memcmp(kSimpleTestFile.data(), buffer, - kSimpleTestFile.size()) == 0) { - LogMessage("SUCCESS: Read file succeeded."); - } else { - LogMessage("ERROR: Read file failed, file contents did not match."); - } - } else { - LogMessage("ERROR: Read file failed."); - } - } - - LogMessage("TEST: Read a sample file with GetFile."); - - // Read the storage file to local file. - { - const size_t kBufferSize = 1024; - char buffer[kBufferSize]; - - // Write file that we're going to upload. - std::string path = PathForResource() + kGetFileTestFile; - // Firebase Storage expects file:// in front of local paths. - std::string file_path = "file://" + path; - - firebase::Future future = - ref.Child("TestFile").Child("File2.txt").GetFile(file_path.c_str()); - WaitForCompletion(future, "Read File"); - - FILE* file = fopen(path.c_str(), "r"); - std::fread(buffer, 1, kBufferSize, file); - fclose(file); - - // Check if the file contents is correct. - if (future.error() == firebase::storage::kErrorNone) { - if (*future.result() != kSimpleTestFile.size()) { - LogMessage( - "ERROR: Read file failed, read incorrect number of bytes (read " - "%z, expected %z)", - *future.result(), kSimpleTestFile.size()); - } else if (memcmp(kSimpleTestFile.data(), buffer, - kSimpleTestFile.size()) == 0) { - LogMessage("SUCCESS: Read file succeeded."); - } else { - LogMessage("ERROR: Read file failed, file contents did not match."); - } - } else { - LogMessage("ERROR: Read file failed."); - } - } - - // Check if the timestamp is correct. - { - LogMessage("TEST: Check sample file metadata."); - - firebase::Future future = - ref.Child("TestFile").Child("File1.txt").GetMetadata(); - WaitForCompletion(future, "GetFileMetadata"); - const firebase::storage::Metadata* metadata = future.result(); - if (future.error() == firebase::storage::kErrorNone) { - // Get the current time to compare to the Timestamp. - int64_t current_time_seconds = static_cast(time(nullptr)); - int64_t updated_time = metadata->updated_time(); - int64_t time_difference = updated_time - current_time_seconds; - // As long as our timestamp is within a day, it's correct enough for - // our purposes. - const int64_t kAllowedTimeDifferenceSeconds = 60L * 60L * 24L; - if (time_difference > kAllowedTimeDifferenceSeconds || - time_difference < -kAllowedTimeDifferenceSeconds) { - LogMessage("ERROR: Incorrect metadata."); - LogMessage(" Timestamp: Got %lld, expected something near %lld", - updated_time, current_time_seconds); - } else { - LogMessage("SUCCESS: Read file successfully."); - } - } else { - LogMessage("ERROR: Read file failed."); - } - // Check custom metadata field to make sure it is empty. - auto custom_metadata = metadata->custom_metadata(); - if (!custom_metadata->empty()) { - LogMessage("ERROR: Metadata reports incorrect custom metadata."); - } else { - LogMessage("SUCCESS: Metadata reports correct custom metadata."); - } - - // Add some values to custom metadata, update and then compare. - custom_metadata->insert(std::make_pair("Key", "Value")); - custom_metadata->insert(std::make_pair("Foo", "Bar")); - firebase::Future custom_metadata_future = - ref.Child("TestFile").Child("File1.txt").UpdateMetadata(metadata); - WaitForCompletion(custom_metadata_future, "UpdateMetadata"); - if (future.error() != firebase::storage::kErrorNone) { - LogMessage("ERROR: UpdateMetadata failed."); - LogMessage(" File1.txt: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Updated Metadata."); - const firebase::storage::Metadata* new_metadata = - custom_metadata_future.result(); - auto new_custom_metadata = new_metadata->custom_metadata(); - auto pair1 = new_custom_metadata->find("Key"); - auto pair2 = new_custom_metadata->find("Foo"); - if (pair1 == new_custom_metadata->end() || pair1->second != "Value" || - pair2 == new_custom_metadata->end() || pair2->second != "Bar") { - LogMessage( - "ERROR: New metadata reports incorrect custom metadata."); - } else { - LogMessage( - "SUCCESS: New metadata reports correct custom metadata."); - } - } - } - - // Check the download URL. - { - LogMessage("TEST: Check for a download URL."); - firebase::Future future = - ref.Child("TestFile").Child("File1.txt").GetDownloadUrl(); - - WaitForCompletion(future, "GetDownloadUrl"); - if (future.error() != firebase::storage::kErrorNone) { - LogMessage("ERROR: Couldn't get download URL."); - LogMessage(" File1.txt: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Got URL: "); - const std::string* download_url = future.result(); - LogMessage(" %s", download_url->c_str()); - } - } - - // Try removing the file. - { - // Call delete on file. - LogMessage("TEST: Removing file."); - firebase::Future delete_future = - ref.Child("TestFile").Child("File1.txt").Delete(); - WaitForCompletion(delete_future, "DeleteFile"); - if (delete_future.error() == firebase::storage::kErrorNone) { - LogMessage("SUCCESS: File was removed."); - } else { - LogMessage("ERROR: File was not removed."); - } - - // Verify it can no longer be read. - const size_t kBufferSize = 1024; - char buffer[kBufferSize]; - firebase::Future read_future = - ref.Child("TestFile") - .Child("File1.txt") - .GetBytes(buffer, kBufferSize); - while (read_future.status() == firebase::kFutureStatusPending) { - ProcessEvents(100); - } - if (read_future.error() == firebase::storage::kErrorObjectNotFound) { - LogMessage("SUCCESS: File could not be read, as expected."); - } else { - LogMessage("ERROR: File could be read after removal. Status = %d: %s", - read_future.error(), read_future.error_message()); + .Child("SampleFile.txt") + .PutBytes(&kSimpleTestFile[0], kSimpleTestFile.size(), metadata); + WaitForCompletion(future, "Write"); + if (future.error() == 0) { + if (future.result()->size_bytes() != kSimpleTestFile.size()) { + LogMessage("ERROR: Incorrect number of bytes uploaded."); } } } - } - - { - const size_t kLargeFileSize = 2 * 1024 * 1024; - std::vector large_test_file; - large_test_file.resize(kLargeFileSize); - for (int i = 0; i < kLargeFileSize; ++i) { - // Fill the buffer with the alphabet. - large_test_file[i] = 'a' + (i % 26); - } - const std::vector& kLargeTestFile = large_test_file; - bool wrote_file = false; { - LogMessage("TEST: Write a large file, pause, and resume mid-way."); - StorageListener listener; - firebase::storage::Controller controller; - firebase::Future future = - ref.Child("TestFile") - .Child("File3.txt") - .PutBytes(kLargeTestFile.data(), kLargeFileSize, &listener, - &controller); - - // Ensure the Controller is valid now that we have associated it with an - // operation. - if (!controller.is_valid()) { - LogMessage("ERROR: Controller was invalid."); - } - - ProcessEvents(500); - // After waiting a moment for the operation to start, pause the operation - // and verify it was successfully paused. Note that pause might not take - // effect immediately, so we give it a few moments to pause before - // failing. - LogMessage("INFO: Pausing."); - if (controller.Pause()) { - ProcessEvents(5000); - if (!listener.on_paused_was_called()) { - LogMessage("ERROR: Listener OnPaused callback was not called."); - } - LogMessage("INFO: Resuming."); - if (!controller.Resume()) { - LogMessage("ERROR: Resume() failed."); - } - } else { - LogMessage("ERROR: Pause() failed."); - } - - WaitForCompletion(future, "WriteLargeFile"); - - // Ensure the progress callback was called. - if (!listener.on_progress_was_called()) { - LogMessage("ERROR: Listener OnProgress callback was not called."); - } - - if (future.error() != firebase::storage::kErrorNone) { - LogMessage("ERROR: Write file failed."); - LogMessage(" TestFile: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Wrote large file."); - wrote_file = true; - auto metadata = future.result(); - if (metadata->size_bytes() == kLargeFileSize) { - LogMessage("SUCCESS: Metadata reports correct size."); - } else { - LogMessage("ERROR: Metadata reports incorrect size."); - LogMessage(" Got %i bytes, expected %i bytes.", - metadata->size_bytes(), kLargeFileSize); + LogMessage("Read back the sample file."); + + const size_t kBufferSize = 1024; + char buffer[kBufferSize]; + + firebase::Future future = ref.Child("TestFile") + .Child("SampleFile.txt") + .GetBytes(buffer, kBufferSize); + WaitForCompletion(future, "Read"); + if (future.error() == 0) { + if (*future.result() != kSimpleTestFile.size()) { + LogMessage("ERROR: Incorrect number of bytes uploaded."); } - } - } - - if (wrote_file) { - LogMessage("TEST: Reading previously-uploaded large file."); - - std::vector buffer; - buffer.resize(kLargeFileSize); - StorageListener listener; - firebase::storage::Controller controller; - firebase::Future future = - ref.Child("TestFile") - .Child("File3.txt") - .GetBytes(buffer.data(), buffer.size(), &listener, &controller); - - // Ensure the Controller is valid now that we have associated it with an - // operation. - if (!controller.is_valid()) { - LogMessage("ERROR: Controller was invalid."); - } - - WaitForCompletion(future, "ReadLargeFile"); - - // Ensure the progress callback was called. - if (!listener.on_progress_was_called()) { - LogMessage("ERROR: Listener OnProgress callback was not called."); - } - - // Check if the file contents is correct. - if (future.error() == firebase::storage::kErrorNone) { - if (*future.result() != kLargeFileSize) { - LogMessage( - "ERROR: Read file failed, read incorrect number of bytes (read " - "%z, expected %z)", - *future.result(), kLargeFileSize); - } else if (std::memcmp(kLargeTestFile.data(), buffer.data(), - kLargeFileSize) == 0) { - LogMessage("SUCCESS: Read file succeeded."); - } else { - LogMessage("ERROR: Read file failed, file contents did not match."); + if (memcmp(&kSimpleTestFile[0], buffer, kSimpleTestFile.size()) != 0) { + LogMessage("ERROR: file contents did not match."); } - } else { - LogMessage("ERROR: Read file failed."); } } - { - LogMessage("TEST: Write a large file and cancel mid-way."); - firebase::storage::Controller controller; - firebase::Future future = - ref.Child("TestFile") - .Child("File4.txt") - .PutBytes(kLargeTestFile.data(), kLargeFileSize, nullptr, - &controller); - - // Ensure the Controller is valid now that we have associated it with an - // operation. - if (!controller.is_valid()) { - LogMessage("ERROR: Controller was invalid."); - } + LogMessage("Delete the sample file."); - // Cancel the operation and verify it was successfully canceled. - controller.Cancel(); - - while (future.status() == firebase::kFutureStatusPending) { - ProcessEvents(100); - } - if (future.error() != firebase::storage::kErrorCancelled) { - LogMessage("ERROR: Write cancellation failed."); - LogMessage(" TestFile: Error %d: %s", future.error(), - future.error_message()); - } else { - LogMessage("SUCCESS: Canceled file upload."); - } + firebase::Future future = + ref.Child("TestFile").Child("SampleFile.txt").Delete(); + WaitForCompletion(future, "Delete"); } } @@ -563,12 +214,14 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Signing out from anonymous account."); auth->SignOut(); + LogMessage("Shutdown the Auth library."); delete auth; auth = nullptr; LogMessage("Shutdown Firebase App."); delete app; + app = nullptr; // Wait until the user wants to quit the app. while (!ProcessEvents(1000)) { diff --git a/storage/testapp/src/desktop/desktop_main.cc b/storage/testapp/src/desktop/desktop_main.cc index 5632a8f9..7f3a95ae 100644 --- a/storage/testapp/src/desktop/desktop_main.cc +++ b/storage/testapp/src/desktop/desktop_main.cc @@ -15,16 +15,39 @@ #include #include #include -#ifndef _WIN32 + +#include // NOLINT + +#ifdef _WIN32 +#include +#define chdir _chdir +#else +#include +#include #include -#endif // !_WIN32 +#endif // _WIN32 #ifdef _WIN32 #include #endif // _WIN32 +#include +#include + #include "main.h" // NOLINT +// 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 + extern "C" int common_main(int argc, const char* argv[]); static bool quit = false; @@ -41,6 +64,8 @@ static BOOL WINAPI SignalHandler(DWORD event) { static void SignalHandler(int /* ignored */) { quit = true; } #endif // _WIN32 +namespace app_framework { + bool ProcessEvents(int msec) { #ifdef _WIN32 Sleep(msec); @@ -51,21 +76,92 @@ bool ProcessEvents(int msec) { } std::string PathForResource() { +#if defined(_WIN32) + // On Windows we should hvae TEST_TMPDIR or TEMP or TMP set. + char buf[MAX_PATH + 1]; + if (GetEnvironmentVariable("TEST_TMPDIR", buf, sizeof(buf)) || + GetEnvironmentVariable("TEMP", buf, sizeof(buf)) || + GetEnvironmentVariable("TMP", buf, sizeof(buf))) { + std::string path(buf); + // Add trailing slash. + if (path[path.size() - 1] != '\\') path += '\\'; + return path; + } +#else + // Linux and OS X should either have the TEST_TMPDIR environment variable set + // or use /tmp. + if (const char* value = getenv("TEST_TMPDIR")) { + std::string path(value); + // Add trailing slash. + if (path[path.size() - 1] != '/') path += '/'; + return path; + } + struct stat s; + if (stat("/tmp", &s) == 0) { + if (s.st_mode & S_IFDIR) { + return std::string("/tmp/"); + } + } +#endif // defined(_WIN32) + // If nothing else, use the current directory. return std::string(); } void LogMessage(const char* format, ...) { va_list list; va_start(list, format); - vprintf(format, list); + LogMessageV(format, list); va_end(list); +} + +void LogMessageV(const char* format, va_list list) { + vprintf(format, list); printf("\n"); fflush(stdout); } WindowContext GetWindowContext() { return nullptr; } +// Change the current working directory to the directory containing the +// specified file. +void ChangeToFileDirectory(const char* file_path) { + std::string path(file_path); + std::replace(path.begin(), path.end(), '\\', '/'); + auto slash = path.rfind('/'); + if (slash != std::string::npos) { + std::string directory = path.substr(0, slash); + if (!directory.empty()) chdir(directory.c_str()); + } +} + +#if defined(_WIN32) +// Returns the number of microseconds since the epoch. +int64_t WinGetCurrentTimeInMicroseconds() { + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + ULARGE_INTEGER now; + now.LowPart = file_time.dwLowDateTime; + now.HighPart = file_time.dwHighDateTime; + + // Windows file time is expressed in 100s of nanoseconds. + // To convert to microseconds, multiply x10. + return now.QuadPart * 10LL; +} +#endif + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + // On desktop, use std::thread as Windows doesn't support pthreads. + std::thread thread(func, data); + thread.detach(); +} + +} // namespace app_framework + int main(int argc, const char* argv[]) { + app_framework::ChangeToFileDirectory(FIREBASE_CONFIG_STRING[0] != '\0' + ? FIREBASE_CONFIG_STRING + : argv[0]); // NOLINT #ifdef _WIN32 SetConsoleCtrlHandler((PHANDLER_ROUTINE)SignalHandler, TRUE); #else diff --git a/storage/testapp/src/ios/ios_main.mm b/storage/testapp/src/ios/ios_main.mm index 715d4119..61a8bc36 100755 --- a/storage/testapp/src/ios/ios_main.mm +++ b/storage/testapp/src/ios/ios_main.mm @@ -14,7 +14,15 @@ #import -#include +#include +#include + +#include +#include +#include +#include +#include +#include #include "main.h" @@ -43,7 +51,7 @@ - (void)viewDidLoad { [super viewDidLoad]; g_parent_view = self.view; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - const char *argv[] = {FIREBASE_TESTAPP_NAME}; + const char *argv[] = {TESTAPP_NAME}; [g_shutdown_signal lock]; g_exit_status = common_main(1, argv); [g_shutdown_complete signal]; @@ -51,6 +59,7 @@ - (void)viewDidLoad { } @end +namespace app_framework { bool ProcessEvents(int msec) { [g_shutdown_signal @@ -71,27 +80,91 @@ WindowContext GetWindowContext() { return g_parent_view; } +void LogMessage(const char *format, ...) { + va_list list; + va_start(list, format); + LogMessageV(format, list); + va_end(list); +} + // Log a message that can be viewed in the console. -void LogMessage(const char* format, ...) { - va_list args; +void LogMessageV(const char *format, va_list list) { NSString *formatString = @(format); - va_start(args, format); - NSString *message = [[NSString alloc] initWithFormat:formatString arguments:args]; - va_end(args); + NSString *message = [[NSString alloc] initWithFormat:formatString arguments:list]; NSLog(@"%@", message); message = [message stringByAppendingString:@"\n"]; + fputs(message.UTF8String, stdout); + fflush(stdout); +} + +// Log a message that can be viewed in the console. +void AddToTextView(const char *str) { + NSString *message = @(str); + dispatch_async(dispatch_get_main_queue(), ^{ g_text_view.text = [g_text_view.text stringByAppendingString:message]; + NSRange range = NSMakeRange(g_text_view.text.length, 0); + [g_text_view scrollRangeToVisible:range]; }); } +// Remove all lines starting with these strings. +static const char *const filter_lines[] = {nullptr}; + +bool should_filter(const char *str) { + for (int i = 0; filter_lines[i] != nullptr; ++i) { + if (strncmp(str, filter_lines[i], strlen(filter_lines[i])) == 0) return true; + } + return false; +} + +void *stdout_logger(void *filedes_ptr) { + int fd = reinterpret_cast(filedes_ptr)[0]; + std::string buffer; + char bufchar; + while (int n = read(fd, &bufchar, 1)) { + if (bufchar == '\0') { + break; + } + buffer = buffer + bufchar; + if (bufchar == '\n') { + if (!should_filter(buffer.c_str())) { + app_framework::AddToTextView(buffer.c_str()); + } + buffer.clear(); + } + } + return nullptr; +} + +void RunOnBackgroundThread(void* (*func)(void*), void* data) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + func(data); + }); +} + +} // namespace app_framework + int main(int argc, char* argv[]) { + // Pipe stdout to call LogToTextView so we can see the gtest output. + int filedes[2]; + assert(pipe(filedes) != -1); + assert(dup2(filedes[1], STDOUT_FILENO) != -1); + pthread_t thread; + pthread_create(&thread, nullptr, app_framework::stdout_logger, reinterpret_cast(filedes)); @autoreleasepool { UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } + // Signal to stdout_logger to exit. + write(filedes[1], "\0", 1); + pthread_join(thread, nullptr); + close(filedes[0]); + close(filedes[1]); + + NSLog(@"Application Exit"); return g_exit_status; } @@ -114,7 +187,7 @@ - (BOOL)application:(UIApplication*)application g_text_view.editable = NO; g_text_view.scrollEnabled = YES; g_text_view.userInteractionEnabled = YES; - + g_text_view.font = [UIFont fontWithName:@"Courier" size:10]; [viewController.view addSubview:g_text_view]; return YES; @@ -125,5 +198,4 @@ - (void)applicationWillTerminate:(UIApplication *)application { [g_shutdown_signal signal]; [g_shutdown_complete wait]; } - @end diff --git a/storage/testapp/src/main.h b/storage/testapp/src/main.h index f5980902..2d30c14e 100644 --- a/storage/testapp/src/main.h +++ b/storage/testapp/src/main.h @@ -15,7 +15,12 @@ #ifndef FIREBASE_TESTAPP_MAIN_H_ // NOLINT #define FIREBASE_TESTAPP_MAIN_H_ // NOLINT +#include + #include +#if !defined(_WIN32) +#include +#endif #if defined(__ANDROID__) #include #include @@ -25,15 +30,18 @@ extern "C" { } // extern "C" #endif // __ANDROID__ -// Defined using -DANDROID_MAIN_APP_NAME=some_app_name when compiling this +// Defined using -DTESTAPP_NAME=some_app_name when compiling this // file. -#ifndef FIREBASE_TESTAPP_NAME -#define FIREBASE_TESTAPP_NAME "android_main" -#endif // FIREBASE_TESTAPP_NAME +#ifndef TESTAPP_NAME +#define TESTAPP_NAME "android_main" +#endif // TESTAPP_NAME + +namespace app_framework { // Cross platform logging method. // Implemented by android/android_main.cc or ios/ios_main.mm. -extern "C" void LogMessage(const char* format, ...); +void LogMessage(const char* format, ...); +void LogMessageV(const char* format, va_list list); // Platform-independent method to flush pending events for the main thread. // Returns true when an event requesting program-exit is received. @@ -42,6 +50,22 @@ bool ProcessEvents(int msec); // Returns a path to a file suitable for the given platform. std::string PathForResource(); +#if defined(_WIN32) +// Windows requires its own version of time-handling code. +int64_t WinGetCurrentTimeInMicroseconds(); +#endif + +// Returns the number of microseconds since the epoch. +static int64_t GetCurrentTimeInMicroseconds() { +#if !defined(_WIN32) + struct timeval now; + gettimeofday(&now, nullptr); + return now.tv_sec * 1000000LL + now.tv_usec; +#else + return WinGetCurrentTimeInMicroseconds(); +#endif +} + // WindowContext represents the handle to the parent window. It's type // (and usage) vary based on the OS. #if defined(__ANDROID__) @@ -64,4 +88,9 @@ jobject GetActivity(); // to the root view of the view controller. WindowContext GetWindowContext(); +// Run the given function on a detached background thread. +void RunOnBackgroundThread(void* (*func)(void* data), void* data); + +} // namespace app_framework + #endif // FIREBASE_TESTAPP_MAIN_H_ // NOLINT diff --git a/storage/testapp/testapp.xcodeproj/project.pbxproj b/storage/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/storage/testapp/testapp.xcodeproj/project.pbxproj +++ b/storage/testapp/testapp.xcodeproj/project.pbxproj @@ -208,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -245,7 +245,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.4; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/storage/testapp/testapp/Info.plist b/storage/testapp/testapp/Info.plist index dc1a95f9..31425994 100644 --- a/storage/testapp/testapp/Info.plist +++ b/storage/testapp/testapp/Info.plist @@ -16,8 +16,6 @@ APPL CFBundleShortVersionString 1.0 - CFBundleSignature - ???? CFBundleURLTypes @@ -27,7 +25,7 @@ google CFBundleURLSchemes - com.googleusercontent.apps.255980362477-3a1nf8c4nl0c7hlnlnmc98hbtg2mnbue + YOUR_REVERSED_CLIENT_ID