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/.google/icon.png b/.google/icon.png new file mode 100644 index 00000000..9430a688 Binary files /dev/null and b/.google/icon.png differ diff --git a/.google/packaging.yaml b/.google/packaging.yaml new file mode 100644 index 00000000..2c061122 --- /dev/null +++ b/.google/packaging.yaml @@ -0,0 +1,14 @@ +# GOOGLE SAMPLE PACKAGING DATA +# +# This file is used by Google as part of our samples packaging process. +# End users may safely ignore this file. It has no relevance to other systems. +--- +status: PUBLISHED +technologies: [Native, Firebase] +categories: [Getting Started] +languages: [C++] +solutions: [Mobile, Monetization, Startup, Enterprise] +github: firebase/quickstart-cpp +level: INTERMEDIATE +icon: .google/icon.png +license: apache2 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 f3786272..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.0.0' - 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.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 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 fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.android.gms:play-services-ads:9.0.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 196ec7fc..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=4.9 -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 18b342d0..00000000 --- a/admob/testapp/readme.md +++ /dev/null @@ -1,137 +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. - -### 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. - -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 47d86c48..00000000 --- a/admob/testapp/src/common_main.cc +++ /dev/null @@ -1,272 +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/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 new_state) { - ::LogMessage("BannerView PresentationState has changed to %d.", new_state); - } - - void OnBoundingBoxChanged(firebase::admob::BannerView* banner_view, - firebase::admob::BoundingBox new_box) { - ::LogMessage( - "BannerView BoundingBox has changed to (x: %d, y: %d, width: %d, " - "height %d).", - new_box.x, new_box.y, new_box.width, new_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 new_state) { - ::LogMessage("InterstitialAd PresentationState has changed to %d.", - new_state); - } -}; - -// 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"; -#else -const char* kBannerAdUnit = "ca-app-pub-3940256099942544/2934735716"; -const char* kInterstitialAdUnit = "ca-app-pub-3940256099942544/4411468910"; -#endif - -// Standard mobile banner size is 320x50. -static const int kBannerWidth = 320; -static const int kBannerHeight = 50; - -// 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("Action failed with error code %d and message \"%s\".", - future.Error(), future.ErrorMessage()); - } -} - -// Execute all methods of the C++ admob API. -extern "C" int common_main(int argc, const char* argv[]) { - namespace admob = ::firebase::admob; - ::firebase::App* app; - LogMessage("Initializing the AdMob library."); -#if defined(__ANDROID__) - app = ::firebase::App::Create(::firebase::AppOptions(), GetJniEnv(), - GetActivity()); -#else - app = ::firebase::App::Create(::firebase::AppOptions()); -#endif // defined(__ANDROID__) - - LogMessage("Created the Firebase App %x.", - static_cast(reinterpret_cast(app))); - - LogMessage("Initializing the AdMob with Firebase API."); - admob::Initialize(*app); - - ::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); - request.test_device_ids = kTestDeviceIDs; - - // Create an ad size for the BannerView. - admob::AdSize ad_size; - ad_size.ad_size_type = admob::kAdSizeStandard; - ad_size.width = kBannerWidth; - ad_size.height = kBannerHeight; - - LogMessage("Creating the BannerView."); - LoggingBannerViewListener banner_listener; - ::firebase::admob::BannerView* banner = new admob::BannerView(); - banner->SetListener(&banner_listener); - banner->Initialize(GetWindowContext(), kBannerAdUnit, ad_size); - - WaitForFutureCompletion(banner->InitializeLastResult()); - - // Make the BannerView visible. - LogMessage("Showing the banner ad."); - banner->Show(); - - WaitForFutureCompletion(banner->ShowLastResult()); - - // When the BannerView is visible, load an ad into it. - LogMessage("Loading a banner ad."); - banner->LoadAd(request); - - WaitForFutureCompletion(banner->LoadAdLastResult()); - - // Move to each of the six pre-defined positions. - LogMessage("Moving the banner ad to top-center."); - banner->MoveTo(admob::BannerView::kPositionTop); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-left."); - banner->MoveTo(admob::BannerView::kPositionTopLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to top-right."); - banner->MoveTo(admob::BannerView::kPositionTopRight); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-center."); - banner->MoveTo(admob::BannerView::kPositionBottom); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-left."); - banner->MoveTo(admob::BannerView::kPositionBottomLeft); - - WaitForFutureCompletion(banner->MoveToLastResult()); - - LogMessage("Moving the banner ad to bottom-right."); - banner->MoveTo(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()); - - // Create and test InterstitialAd. - LogMessage("Creating the InterstitialAd."); - LoggingInterstitialAdListener interstitial_listener; - ::firebase::admob::InterstitialAd* interstitial = new admob::InterstitialAd(); - interstitial->SetListener(&interstitial_listener); - interstitial->Initialize(GetWindowContext(), kInterstitialAdUnit); - - WaitForFutureCompletion(interstitial->InitializeLastResult()); - - // 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(); - - // Wait until the user kills the app. - while (!ProcessEvents(1000)) { - } - - delete interstitial; - delete banner; - admob::Terminate(); - delete app; - - return 0; -} diff --git a/analytics/testapp/AndroidManifest.xml b/analytics/testapp/AndroidManifest.xml index 5bc6e427..b7038a15 100644 --- a/analytics/testapp/AndroidManifest.xml +++ b/analytics/testapp/AndroidManifest.xml @@ -6,9 +6,12 @@ - + - + diff --git a/analytics/testapp/CMakeLists.txt b/analytics/testapp/CMakeLists.txt new file mode 100644 index 00000000..cd156ba0 --- /dev/null +++ b/analytics/testapp/CMakeLists.txt @@ -0,0 +1,119 @@ +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_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 69982a69..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.0.0' - 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.2' + 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 fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.firebase:firebase-analytics:9.0.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 196ec7fc..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=4.9 -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 b86b20d8..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/android/java/com/google/firebase/example/LoggingUtils.java b/analytics/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index acbd8d3e..11d67c5b 100644 --- a/analytics/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/analytics/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(); diff --git a/analytics/testapp/src/common_main.cc b/analytics/testapp/src/common_main.cc index 6ebf7e1f..e063e416 100644 --- a/analytics/testapp/src/common_main.cc +++ b/analytics/testapp/src/common_main.cc @@ -28,10 +28,9 @@ extern "C" int common_main(int argc, const char* argv[]) { LogMessage("Initialize the Analytics library"); #if defined(__ANDROID__) - app = ::firebase::App::Create(::firebase::AppOptions(), GetJniEnv(), - GetActivity()); + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); #else - app = ::firebase::App::Create(::firebase::AppOptions()); + app = ::firebase::App::Create(); #endif // defined(__ANDROID__) LogMessage("Created the firebase app %x", @@ -41,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. @@ -52,6 +63,10 @@ extern "C" int common_main(int argc, const char* argv[]) { // Set the user ID. analytics::SetUserId("uber_user_510"); + 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."); analytics::LogEvent(analytics::kEventLogin); diff --git a/analytics/testapp/src/desktop/desktop_main.cc b/analytics/testapp/src/desktop/desktop_main.cc index 00e57132..0220c688 100644 --- a/analytics/testapp/src/desktop/desktop_main.cc +++ b/analytics/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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/src/ios/ios_main.mm b/analytics/testapp/src/ios/ios_main.mm index 6ccb2de5..2adcac9c 100755 --- a/analytics/testapp/src/ios/ios_main.mm +++ b/analytics/testapp/src/ios/ios_main.mm @@ -101,6 +101,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/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 2193e51b..540dec54 100644 --- a/auth/testapp/AndroidManifest.xml +++ b/auth/testapp/AndroidManifest.xml @@ -6,10 +6,12 @@ - + + android:exported = "true" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize"> diff --git a/auth/testapp/CMakeLists.txt b/auth/testapp/CMakeLists.txt new file mode 100644 index 00000000..f5aa704f --- /dev/null +++ b/auth/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_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 6e63752d..eb739709 100644 --- a/auth/testapp/build.gradle +++ b/auth/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.0.0' - 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.2' + 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 fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.firebase:firebase-auth:9.0.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 196ec7fc..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=4.9 -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 7506b53d..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/LoggingUtils.java b/auth/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index acbd8d3e..11d67c5b 100644 --- a/auth/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/auth/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(); 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 c1b29d61..f0d12c7b 100644 --- a/auth/testapp/src/common_main.cc +++ b/auth/testapp/src/common_main.cc @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include #include #include #include #include "firebase/app.h" #include "firebase/auth.h" +#include "firebase/auth/credential.h" +#include "firebase/util.h" // Thin OS abstraction layer. #include "main.h" // NOLINT @@ -27,19 +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::kAuthErrorUnimplemented; +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; @@ -47,14 +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 kTestDisplayName[] = "my display name"; -static const char kTestPhotoUri[] = "myphotos.example.com"; +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__) @@ -66,58 +87,84 @@ 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, - AuthError expected_error) { +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) { - LogMessage("ERROR! Future for %s is invalid", fn); + if (future.status() == ::firebase::kFutureStatusInvalid) { + LogMessage("ERROR: Future for %s is invalid", fn); return false; } // Wait for future to complete. LogMessage(" Calling %s...", fn); - while (future.Status() == ::firebase::kFutureStatusPending) { + while (future.status() == ::firebase::kFutureStatusPending) { if (ProcessEvents(100)) return true; } // Log error result. - const AuthError error = static_cast(future.Error()); - if (error == expected_error) { - const char* error_message = future.ErrorMessage(); - if (error_message) { - LogMessage("%s completed as expected", fn); + if (log_error) { + const AuthError error = static_cast(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("%s completed as expected, error: %d '%s'", fn, error, - error_message); + LogMessage("ERROR: %s completed with error: %d, `%s`", fn, error, + future.error_message()); } - } else { - LogMessage("ERROR! %s completed with error: %d, `%s`", fn, error, - future.ErrorMessage()); } 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->CurrentUser(); + const User sign_in_user = sign_in_future.result() ? *sign_in_future.result() : + User(); + const User auth_user = auth->current_user(); - if (sign_in_user != auth_user) { - LogMessage("ERROR: future's user (%x) and CurrentUser (%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; +} + +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 (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; +} + +// 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; } @@ -131,7 +178,7 @@ static std::string CreateNewEmail() { static void ExpectFalse(const char* test, bool value) { if (value) { - LogMessage("ERROR! %s is true instead of false", test); + LogMessage("ERROR: %s is true instead of false", test); } else { LogMessage("%s is false, as expected", test); } @@ -141,7 +188,7 @@ static void ExpectTrue(const char* test, bool value) { if (value) { LogMessage("%s is true, as expected", test); } else { - LogMessage("ERROR! %s is false instead of true", test); + LogMessage("ERROR: %s is false instead of true", test); } } @@ -151,15 +198,141 @@ static void ExpectStringsEqual(const char* test, const char* expected, if (strcmp(expected, actual) == 0) { LogMessage("%s is '%s' as expected", test, actual); } else { - LogMessage("ERROR! %s is '%s' instead of '%s'", test, actual, expected); + LogMessage("ERROR: %s is '%s' instead of '%s'", test, actual, 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) { // 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); + } + + void CompleteTest(const char* test_name, int min_state_changes, + int max_state_changes) { + const bool success = min_state_changes <= num_state_changes_ && + num_state_changes_ <= max_state_changes; + LogMessage("%sAuthStateListener called %d time%s on %s.", + success ? "" : "ERROR: ", num_state_changes_, + num_state_changes_ == 1 ? "" : "s", test_name); + num_state_changes_ = 0; + } + + private: + 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: UserLogin(Auth* auth, const std::string& email, const std::string& password) - : auth_(auth), email_(email), password_(password), user_(nullptr) {} + : auth_(auth), + email_(email), + password_(password), + log_errors_(true) {} explicit UserLogin(Auth* auth) : auth_(auth) { email_ = CreateNewEmail(); @@ -167,46 +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() pre-delete signin", + "Auth::SignInWithCredential() for UserLogin", kAuthErrorNone, auth_); } void Delete() { - if (user_ != nullptr) { - Future delete_future = user_->Delete(); - if (delete_future.Status() == ::firebase::kFutureStatusInvalid) { + 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); + WaitForFuture(delete_future, "User::Delete()", kAuthErrorNone, + log_errors_); } - user_ = nullptr; + user_ = User(); } - const char* email() const { return email_.c_str(); } + 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; } @@ -214,7 +389,68 @@ 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. @@ -222,23 +458,64 @@ extern "C" int common_main(int argc, const char* argv[]) { App* app; LogMessage("Starting Auth tests."); -// Create the App wrapper. #if defined(__ANDROID__) - app = App::Create(AppOptions(), GetJniEnv(), GetActivity()); + app = App::Create(GetJniEnv(), GetActivity()); #else - app = App::Create(AppOptions()); + app = App::Create(); #endif // defined(__ANDROID__) + LogMessage("Created the Firebase app %x.", static_cast(reinterpret_cast(app))); // Create the Auth class for that App. + + ::firebase::ModuleInitializer initializer; + initializer.Initialize(app, nullptr, [](::firebase::App* app, void*) { + ::firebase::InitResult init_result; + Auth::GetAuth(app, &init_result); + return init_result; + }); + while (initializer.InitializeLastResult().status() != + firebase::kFutureStatusComplete) { + if (ProcessEvents(100)) return 1; // exit if requested + } + + if (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Auth: %s", + initializer.InitializeLastResult().error_message()); + ProcessEvents(2000); + return 1; + } + Auth* auth = Auth::GetAuth(app); + LogMessage("Created the Auth %x class for the Firebase app.", static_cast(reinterpret_cast(auth))); - // Test that CurrentUser() returns NULL right after creation. - if (auth->CurrentUser() != nullptr) { - LogMessage("ERROR: CurrentUser() returning %x instead of NULL", - auth->CurrentUser()); + // 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().is_valid()) { + LogMessage("No user signed in at creation time."); + } else { + 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(); + } + + // --- Credential copy tests ------------------------------------------------- + { + Credential email_cred = + EmailAuthProvider::GetCredential(kCustomEmail, kCustomPassword); + Credential facebook_cred = + FacebookAuthProvider::GetCredential(kTestAccessTokenBad); + + // Test copy constructor. + Credential cred_copy(email_cred); + + // Test assignment operator. + cred_copy = facebook_cred; + (void)cred_copy; } // --- Custom Profile tests -------------------------------------------------- @@ -246,45 +523,168 @@ 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 " "(custom) email and password", kAuthErrorNone, auth); // Test SignOut() after signed in with email and password. - if (sign_in_future.Status() == ::firebase::kFutureStatusComplete) { + if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->CurrentUser() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: CurrentUser() returning %x instead of NULL after " + "ERROR: current_user() returning %s instead of nullptr after " "SignOut()", - auth->CurrentUser()); + auth->current_user().uid().c_str()); + } + } + } + } + + // --- StateChange tests ----------------------------------------------------- + { + AuthStateChangeCounter counter; + IdTokenChangeCounter token_counter; + + // Test notification on registration. + auth->AddAuthStateListener(&counter); + 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(); + 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. + LogMessage("Current user %s", auth->current_user().uid().c_str()); // DEBUG + auth->SignOut(); + // 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()) { - LogMessage("Error - Could not create in with 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) { + if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->CurrentUser() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: CurrentUser() returning %x instead of NULL after " + "ERROR: current_user() returning valid user %s instead of invalid user after " "SignOut()", - auth->CurrentUser()); + auth->current_user().uid().c_str()); } } } @@ -295,7 +695,11 @@ extern "C" int common_main(int argc, const char* argv[]) { auth->FetchProvidersForEmail(user_login.email()); WaitForFuture(providers_future, "Auth::FetchProvidersForEmail()", kAuthErrorNone); - const Auth::FetchProvidersResult* pro = providers_future.Result(); + 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(), pro->providers.size()); @@ -309,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) { + if (sign_in_future.status() == ::firebase::kFutureStatusComplete) { auth->SignOut(); - if (auth->CurrentUser() != nullptr) { + if (auth->current_user().is_valid()) { LogMessage( - "ERROR: CurrentUser() returning %x instead of NULL after " - "SignOut()", - auth->CurrentUser()); + "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. @@ -361,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); } @@ -420,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. @@ -428,146 +1034,204 @@ 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()); - ExpectStringsEqual("Anonymous user Email", "", - anonymous_user->Email().c_str()); - ExpectStringsEqual("Anonymous user DisplayName", "", - anonymous_user->DisplayName().c_str()); - ExpectStringsEqual("Anonymous user PhotoUrl", "", - anonymous_user->PhotoUrl().c_str()); - ExpectStringsEqual("Anonymous user ProviderId", kFirebaseProviderId, - anonymous_user->ProviderId().c_str()); - ExpectTrue("Anonymous email Anonymous()", anonymous_user->Anonymous()); - - // Test User::LinkWithCredential(). + if (anon_sign_in_for_user.status() == ::firebase::kFutureStatusComplete) { + 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()); + ExpectStringsEqual("Anonymous user display_name", "", + anonymous_user.display_name().c_str()); + ExpectStringsEqual("Anonymous user photo_url", "", + anonymous_user.photo_url().c_str()); + ExpectStringsEqual("Anonymous user provider_id", kFirebaseProviderId, + 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()); - ExpectStringsEqual("Email user Email", user_login.email(), - email_user->Email().c_str()); - ExpectStringsEqual("Email user DisplayName", "", - email_user->DisplayName().c_str()); - ExpectStringsEqual("Email user PhotoUrl", "", - email_user->PhotoUrl().c_str()); - ExpectStringsEqual("Email user ProviderId", kFirebaseProviderId, - email_user->ProviderId().c_str()); - ExpectFalse("Email email Anonymous()", email_user->Anonymous()); - - // Test User::Token(). + 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()); + ExpectStringsEqual("Email user display_name", "", + email_user.display_name().c_str()); + ExpectStringsEqual("Email user photo_url", "", + email_user.photo_url().c_str()); + ExpectStringsEqual("Email user provider_id", kFirebaseProviderId, + 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->Token(false); - WaitForFuture(token_no_refresh, "User::Token(false)", + Future token_no_refresh = email_user.GetToken(false); + WaitForFuture(token_no_refresh, "User::GetToken(false)", kAuthErrorNone); - LogMessage("User::Token(false) = %s", - token_no_refresh.Result() - ? token_no_refresh.Result()->c_str() + LogMessage("User::GetToken(false) = %s", + token_no_refresh.result() + ? token_no_refresh.result()->c_str() : ""); // with force_refresh = true. - Future token_force_refresh = email_user->Token(true); - WaitForFuture(token_force_refresh, "User::Token(true)", + Future token_force_refresh = + email_user.GetToken(true); + WaitForFuture(token_force_refresh, "User::GetToken(true)", kAuthErrorNone); - LogMessage("User::Token(true) = %s", - token_force_refresh.Result() - ? token_force_refresh.Result()->c_str() + LogMessage("User::GetToken(true) = %s", + token_force_refresh.result() + ? token_force_refresh.result()->c_str() : ""); // Test Reload(). - Future reload_future = email_user->Reload(); + Future reload_future = email_user.Reload(); WaitForFuture(reload_future, "User::Reload()", kAuthErrorNone); - // Test User::RefreshToken(). - const std::string refresh_token = email_user->RefreshToken(); - LogMessage("User::RefreshToken() = %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->CurrentUser() == 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_user = email_sign_in_again.result() + ? email_sign_in_again.result()->user + : User(); } } - if (email_user != nullptr) { - // Test User::ProviderData(). - const std::vector& provider_data = - email_user->ProviderData(); - LogMessage("User::ProviderData() returned %d interface%s", + if (email_user.is_valid()) { + // Test 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->DisplayName().c_str(), - user_info->PhotoUrl().c_str(), - user_info->ProviderId().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); @@ -575,21 +1239,15 @@ 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(); - WaitForFuture( - send_email_verification_future, "User::SendEmailVerification()", -#if defined(__ANDROID__) - // Known issue: this method isn't implemented on Android yet. - kAuthErrorUnimplemented); -#else // !defined(__ANDROID__) - kAuthErrorNone); -#endif // !defined(__ANDROID__) + email_user.SendEmailVerification(); + WaitForFuture(send_email_verification_future, + "User::SendEmailVerification()", kAuthErrorNone); } } } @@ -597,21 +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(); + 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 00e57132..0220c688 100644 --- a/auth/testapp/src/desktop/desktop_main.cc +++ b/auth/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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/invites/testapp/AndroidManifest.xml b/database/testapp/AndroidManifest.xml similarity index 57% rename from invites/testapp/AndroidManifest.xml rename to database/testapp/AndroidManifest.xml index 4c24b740..f94a8ca5 100644 --- a/invites/testapp/AndroidManifest.xml +++ b/database/testapp/AndroidManifest.xml @@ -1,12 +1,17 @@ - + + + - + diff --git a/database/testapp/CMakeLists.txt b/database/testapp/CMakeLists.txt new file mode 100644 index 00000000..d505b0c9 --- /dev/null +++ b/database/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 iphlpapi psapi userenv shell32) + 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_database firebase_auth firebase_app) +target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS}) diff --git a/admob/testapp/LICENSE b/database/testapp/LICENSE similarity index 100% rename from admob/testapp/LICENSE rename to database/testapp/LICENSE diff --git a/admob/testapp/LaunchScreen.storyboard b/database/testapp/LaunchScreen.storyboard similarity index 100% rename from admob/testapp/LaunchScreen.storyboard rename to database/testapp/LaunchScreen.storyboard diff --git a/database/testapp/Podfile b/database/testapp/Podfile new file mode 100644 index 00000000..5832b272 --- /dev/null +++ b/database/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# Firebase Realtime Database test application. +target 'testapp' do + 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 new file mode 100644 index 00000000..bfa91283 --- /dev/null +++ b/database/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.database.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 + database +} + +apply plugin: 'com.google.gms.google-services' 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/admob/testapp/gradle/wrapper/gradle-wrapper.jar b/database/testapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from admob/testapp/gradle/wrapper/gradle-wrapper.jar rename to database/testapp/gradle/wrapper/gradle-wrapper.jar diff --git a/invites/testapp/gradle/wrapper/gradle-wrapper.properties b/database/testapp/gradle/wrapper/gradle-wrapper.properties similarity index 52% rename from invites/testapp/gradle/wrapper/gradle-wrapper.properties rename to database/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/invites/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/admob/testapp/gradlew b/database/testapp/gradlew similarity index 100% rename from admob/testapp/gradlew rename to database/testapp/gradlew diff --git a/admob/testapp/gradlew.bat b/database/testapp/gradlew.bat similarity index 100% rename from admob/testapp/gradlew.bat rename to database/testapp/gradlew.bat diff --git a/invites/testapp/proguard.pro b/database/testapp/proguard.pro similarity index 85% rename from invites/testapp/proguard.pro rename to database/testapp/proguard.pro index c7e9278d..54cd248b 100644 --- a/invites/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 new file mode 100644 index 00000000..7841f590 --- /dev/null +++ b/database/testapp/readme.md @@ -0,0 +1,248 @@ +Firebase Realtime Database Quickstart +======================== + +The Firebase Realtime Database Test Application (testapp) demonstrates Firebase +Realtime Database operations with the Firebase Realtime Database 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 Realtime Database C++ + and Firebase Auth C++ libraries. + - Gets a pointer to firebase::Auth, and signs in anonymously. This allows the + testapp to access a Firebase Database instance with authentication rules + enabled, which is the default setting in Firebase Console. + - Gets a DatabaseReference to the root node's "test_app_data" child, uses + DatabaseReference::PushChild() to create a child with a unique key + underneath it to work in, and gets a reference to that child, which the + testapp will use for the remainder of its actions. + - Sets some simple values (numbers, bools, strings, timestamp) and reads them + back to ensure the database can be read from and written to. + - Runs a transaction, using DatabaseReference::RunTransaction(), and validates + that its results were applied properly. + - Runs DatabaseReference::UpdateChildren to update multiple children at once. + - Uses Query to narrow down the view from a DatabaseReference. + - Sets up a ValueListener to watch for data value changes at a given database + location. + - Sets up a ChildListener to watch for changes in the list of children at + a database location. + - Sets up OnDisconnect actions to make changes to the database on disconnect, + then disconnects from the database to confirm the actions are performed. + - Shuts down the Firebase Database, Firebase Auth, and Firebase App systems. + +Introduction +------------ + +- [Read more about Firebase Realtime Database](https://firebase.google.com/docs/database/) + +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 the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.database.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 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](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_database.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.database.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 Database, 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. + - 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 +------------ + + - Due to the way Firebase Realtime Database interacts with JSON, it is + possible that if you set a location to an integral value using a + firebase::Variant of type Int64, you may get back a firebase::Variant of + type Double when later retrieving the data from the database. Be sure to + check the types of Variants or use methods such as Variant::AsInt64() to + coerce their types before accessing the data within them. + +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/invites/testapp/res/layout/main.xml b/database/testapp/res/layout/main.xml similarity index 100% rename from invites/testapp/res/layout/main.xml rename to database/testapp/res/layout/main.xml diff --git a/invites/testapp/res/values/strings.xml b/database/testapp/res/values/strings.xml similarity index 52% rename from invites/testapp/res/values/strings.xml rename to database/testapp/res/values/strings.xml index 2e7bcd10..eef9e385 100644 --- a/invites/testapp/res/values/strings.xml +++ b/database/testapp/res/values/strings.xml @@ -1,4 +1,4 @@ - Firebase Invites Test + Firebase Database Test 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/admob/testapp/src/android/android_main.cc b/database/testapp/src/android/android_main.cc similarity index 100% rename from admob/testapp/src/android/android_main.cc rename to database/testapp/src/android/android_main.cc diff --git a/invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/database/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java similarity index 98% rename from invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java rename to database/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java index acbd8d3e..11d67c5b 100644 --- a/invites/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/database/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(); diff --git a/database/testapp/src/common_main.cc b/database/testapp/src/common_main.cc new file mode 100644 index 00000000..3a35179b --- /dev/null +++ b/database/testapp/src/common_main.cc @@ -0,0 +1,1053 @@ +// 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 "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/database.h" +#include "firebase/future.h" +#include "firebase/util.h" + +// Thin OS abstraction layer. +#include "main.h" // NOLINT + +// An example of a ValueListener object. This specific version will +// simply log every value it sees, and store them in a list so we can +// confirm that all values were received. +class SampleValueListener : public firebase::database::ValueListener { + public: + void OnValueChanged( + const firebase::database::DataSnapshot& snapshot) override { + LogMessage(" ValueListener.OnValueChanged(%s)", + snapshot.value().AsString().string_value()); + last_seen_value_ = snapshot.value(); + seen_values_.push_back(snapshot.value()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogMessage("ERROR: SampleValueListener canceled: %d: %s", error_code, + error_message); + } + const firebase::Variant& last_seen_value() { return last_seen_value_; } + bool seen_value(const firebase::Variant& value) { + return std::find(seen_values_.begin(), seen_values_.end(), value) != + seen_values_.end(); + } + size_t num_seen_values() { return seen_values_.size(); } + + private: + firebase::Variant last_seen_value_; + std::vector seen_values_; +}; + +// An example ChildListener class. +class SampleChildListener : public firebase::database::ChildListener { + public: + void OnChildAdded(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildAdded(%s)", snapshot.key()); + events_.push_back(std::string("added ") + snapshot.key()); + } + void OnChildChanged(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildChanged(%s)", snapshot.key()); + events_.push_back(std::string("changed ") + snapshot.key()); + } + void OnChildMoved(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildMoved(%s)", snapshot.key()); + events_.push_back(std::string("moved ") + snapshot.key()); + } + void OnChildRemoved( + const firebase::database::DataSnapshot& snapshot) override { + LogMessage(" ChildListener.OnChildRemoved(%s)", snapshot.key()); + events_.push_back(std::string("removed ") + snapshot.key()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogMessage("ERROR: SampleChildListener canceled: %d: %s", error_code, + error_message); + } + + // Get the total number of Child events this listener saw. + size_t total_events() { return events_.size(); } + + // Get the number of times this event was seen. + int num_events(const std::string& event) { + int count = 0; + for (int i = 0; i < events_.size(); i++) { + if (events_[i] == event) { + count++; + } + } + return count; + } + + public: + // Vector of strings defining the events we saw, in order. + std::vector events_; +}; + +// A ValueListener that expects a specific value to be set. +class ExpectValueListener : public firebase::database::ValueListener { + public: + explicit ExpectValueListener(firebase::Variant wait_value) + : wait_value_(wait_value.AsString()), got_value_(false) {} + void OnValueChanged( + 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, + const char* error_message) override { + LogMessage("ERROR: ExpectValueListener canceled: %d: %s", error_code, + error_message); + } + + bool got_value() { return got_value_; } + + private: + firebase::Variant wait_value_; + bool got_value_; +}; + +// 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); + } + if (future.status() != firebase::kFutureStatusComplete) { + LogMessage("ERROR: %s returned an invalid result.", name); + } else if (future.error() != 0) { + LogMessage("ERROR: %s returned error %d: %s", name, future.error(), + future.error_message()); + } +} + +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("Initialize Firebase Auth and Firebase Database."); + + // Use ModuleInitializer to initialize both Auth and Database, ensuring no + // dependencies are missing. + ::firebase::database::Database* database = nullptr; + ::firebase::auth::Auth* auth = nullptr; + void* initialize_targets[] = {&auth, &database}; + + 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 Firebase Database."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::database::Database**>(targets[1]) = + ::firebase::database::Database::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 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 = + 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 the database anyway. This may fail " + "depending on the security settings."); + } + } + + 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; + ref = database->GetReference("test_app_data").PushChild(); + + saved_url = ref.url(); + LogMessage("URL: %s", saved_url.c_str()); + + // Set and Get some simple fields. This will set a string, integer, double, + // bool, and current timestamp, and then read them back from the database to + // confirm that they were set. Then it will remove the string value. + { + const char* kSimpleString = "Some simple string"; + const int kSimpleInt = 2; + const int kSimplePriority = 100; + const double kSimpleDouble = 3.4; + const bool kSimpleBool = true; + + { + LogMessage("TEST: Set simple values."); + firebase::Future f1 = + ref.Child("Simple").Child("String").SetValue(kSimpleString); + firebase::Future f2 = + ref.Child("Simple").Child("Int").SetValue(kSimpleInt); + firebase::Future f3 = + ref.Child("Simple").Child("Double").SetValue(kSimpleDouble); + firebase::Future f4 = + ref.Child("Simple").Child("Bool").SetValue(kSimpleBool); + firebase::Future f5 = + 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 || + 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."); + } + } + // Get the values that we just set, and confirm that they match what we + // set them to. + { + LogMessage("TEST: Get simple values."); + firebase::Future f1 = + ref.Child("Simple").Child("String").GetValue(); + firebase::Future f2 = + ref.Child("Simple").Child("Int").GetValue(); + firebase::Future f3 = + ref.Child("Simple").Child("Double").GetValue(); + firebase::Future f4 = + 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 && + 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 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."); + LogMessage(" String: Got \"%s\", expected \"%s\"", + f1.result()->value().string_value(), kSimpleString); + LogMessage(" Int: Got %lld, expected %d", + f2.result()->value().AsInt64().int64_value(), kSimpleInt); + LogMessage(" Double: Got %lf, expected %lf", + f3.result()->value().AsDouble().double_value(), + kSimpleDouble); + LogMessage( + " Bool: Got %s, expected %s", + f4.result()->value().AsBool().bool_value() ? "true" : "false", + kSimpleBool ? "true" : "false"); + 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."); + } + } else { + LogMessage("ERROR: Get simple values failed."); + } + + // Try removing one value. + { + LogMessage("TEST: Removing a value."); + WaitForCompletion(ref.Child("Simple").Child("String").RemoveValue(), + "RemoveSimpleString"); + firebase::Future future = + ref.Child("Simple").Child("String").GetValue(); + WaitForCompletion(future, "GetRemovedSimpleString"); + if (future.error() == firebase::database::kErrorNone && + future.result()->value().is_null()) { + LogMessage("SUCCESS: Value was removed."); + } else { + LogMessage("ERROR: Value was not removed."); + } + } + } + } + +#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. + { + firebase::Future transaction_future; + static const int kInitialScore = 500; + static const int kAddedScore = 100; + LogMessage("TEST: Run transaction."); + // Set an initial score of 500 points. + WaitForCompletion(ref.Child("TransactionResult") + .Child("player_score") + .SetValue(kInitialScore), + "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, + 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 + // error. + if (transaction_future.error() == firebase::database::kErrorNone) { + LogMessage("SUCCESS: Transaction committed."); + } else if (transaction_future.error() == + firebase::database::kErrorTransactionAbortedByUser) { + LogMessage("ERROR: Transaction was aborted."); + } else { + LogMessage("ERROR: Transaction returned error %d: %s", + transaction_future.error(), + transaction_future.error_message()); + } + + // If the transaction succeeded, let's read back the values that were + // written to confirm they match. + if (transaction_future.error() == firebase::database::kErrorNone) { + LogMessage("TEST: Test reading transaction results."); + + firebase::Future read_future = + ref.Child("TransactionResult").GetValue(); + WaitForCompletion(read_future, "ReadTransactionResults"); + if (read_future.error() != firebase::database::kErrorNone) { + LogMessage("ERROR: Error %d reading transaction results: %s", + read_future.error(), read_future.error_message()); + } else { + const firebase::database::DataSnapshot& result = *read_future.result(); + if (result.children_count() == 3 && result.HasChild("player_item") && + result.Child("player_item").value() == "Fire sword" && + result.HasChild("player_class") && + result.Child("player_class").value() == "Warrior" && + result.HasChild("player_score") && + result.Child("player_score").value().AsInt64() == + kInitialScore + kAddedScore) { + if (result.value() != transaction_future.result()->value()) { + LogMessage( + "ERROR: Transaction snapshot did not match newly read data."); + } else { + LogMessage("SUCCESS: Transaction test succeeded."); + } + } else { + LogMessage("ERROR: Transaction result was incorrect."); + } + } + } + } + + // Set up a map of values that we will put into the database, then modify. + std::map sample_values; + sample_values.insert(std::make_pair("Apple", 1)); + sample_values.insert(std::make_pair("Banana", 2)); + sample_values.insert(std::make_pair("Cranberry", 3)); + sample_values.insert(std::make_pair("Durian", 4)); + sample_values.insert(std::make_pair("Eggplant", 5)); + + // Run UpdateChildren, specifying some existing children (which will be + // modified), some children with a value of null (which will be removed), + // and some new children (which will be added). + { + LogMessage("TEST: UpdateChildren."); + + WaitForCompletion(ref.Child("UpdateChildren").SetValue(sample_values), + "UpdateSetValues"); + + // Set each key's value to what's given in this map. We use a map of + // Variant so that we can specify Variant::Null() to remove a key from the + // database. + std::map update_values; + update_values.insert(std::make_pair("Apple", 100)); + update_values.insert(std::make_pair("Durian", "is a fruit!")); + update_values.insert(std::make_pair("Eggplant", firebase::Variant::Null())); + update_values.insert(std::make_pair("Fig", 6)); + + WaitForCompletion(ref.Child("UpdateChildren").UpdateChildren(update_values), + "UpdateChildren"); + + // Get the values that were written to ensure they were updated properly. + firebase::Future updated_values = + ref.Child("UpdateChildren").GetValue(); + WaitForCompletion(updated_values, "UpdateChildrenResult"); + if (updated_values.error() == firebase::database::kErrorNone) { + const firebase::database::DataSnapshot& result = *updated_values.result(); + bool failed = false; + if (result.children_count() != 5) { + LogMessage( + "ERROR: UpdateChildren returned an unexpected number of " + "children: " + "%d", + result.children_count()); + failed = true; + } + if (!result.HasChild("Apple") || + result.Child("Apple").value().AsInt64() != 100) { + LogMessage("ERROR: Child key 'Apple' was not updated correctly."); + failed = true; + } + if (!result.HasChild("Banana") || + result.Child("Banana").value().AsInt64() != 2) { + LogMessage("ERROR: Child key 'Banana' was not updated correctly."); + failed = true; + } + if (!result.HasChild("Cranberry") || + result.Child("Cranberry").value().AsInt64() != 3) { + LogMessage("ERROR: Child key 'Cranberry' was not updated correctly."); + failed = true; + } + if (!result.HasChild("Durian") || + result.Child("Durian").value().AsString() != "is a fruit!") { + LogMessage("ERROR: Child key 'Durian' was not updated correctly."); + failed = true; + } + if (result.HasChild("Eggplant")) { + LogMessage("ERROR: Child key 'Eggplant' was not removed."); + failed = true; + } + if (!result.HasChild("Fig") || + result.Child("Fig").value().AsInt64() != 6) { + LogMessage("ERROR: Child key 'Fig' was not added correctly."); + failed = true; + } + if (!failed) { + LogMessage("SUCCESS: UpdateChildren succeeded."); + } else { + LogMessage( + "ERROR: UpdateChildren did not modify the children as expected."); + } + } else { + LogMessage("ERROR: Couldn't retrieve updated values."); + } + } + + // Test Query, which gives you different views into the same location in the + // database. + { + LogMessage("TEST: Query filtering."); + + firebase::Future set_future = + ref.Child("QueryFiltering").SetValue(sample_values); + WaitForCompletion(set_future, "QuerySetValues"); + // Create a query for keys in the lexicographical range "B" to "Dz". + auto b_to_d = ref.Child("QueryFiltering") + .OrderByKey() + .StartAt("B") + .EndAt("Dz") + .GetValue(); + // Create a query for values in the numeric range 1 to 3. + auto one_to_three = ref.Child("QueryFiltering") + .OrderByValue() + .StartAt(1) + .EndAt(3) + .GetValue(); + // Create a query ordered by value, but limited to only the highest two + // values. + auto four_and_five = + ref.Child("QueryFiltering").OrderByValue().LimitToLast(2).GetValue(); + // Create a query ordered by key, but limited to only the lowest two keys. + auto a_and_b = + ref.Child("QueryFiltering").OrderByKey().LimitToFirst(2).GetValue(); + // Create a query limited only to the key "Cranberry". + auto c_only = ref.Child("QueryFiltering") + .OrderByKey() + .EqualTo("Cranberry") + .GetValue(); + + WaitForCompletion(b_to_d, "QueryBthruD"); + WaitForCompletion(one_to_three, "Query1to3"); + WaitForCompletion(four_and_five, "Query4and5"); + WaitForCompletion(a_and_b, "QueryAandB"); + WaitForCompletion(c_only, "QueryC"); + + bool failed = false; + // Check that the queries each returned the expected results. + if (b_to_d.error() != firebase::database::kErrorNone || + b_to_d.result()->children_count() != 3 || + !b_to_d.result()->HasChild("Banana") || + !b_to_d.result()->HasChild("Cranberry") || + !b_to_d.result()->HasChild("Durian")) { + LogMessage("ERROR: Query B-to-D returned unexpected results."); + failed = true; + } + if (one_to_three.error() != firebase::database::kErrorNone || + one_to_three.result()->children_count() != 3 || + !one_to_three.result()->HasChild("Apple") || + !one_to_three.result()->HasChild("Banana") || + !one_to_three.result()->HasChild("Cranberry")) { + LogMessage("ERROR: Query 1-to-3 returned unexpected results."); + failed = true; + } + if (four_and_five.error() != firebase::database::kErrorNone || + four_and_five.result()->children_count() != 2 || + !four_and_five.result()->HasChild("Durian") || + !four_and_five.result()->HasChild("Eggplant")) { + LogMessage("ERROR: Query 4-and-5 returned unexpected results."); + failed = true; + } + if (a_and_b.error() != firebase::database::kErrorNone || + a_and_b.result()->children_count() != 2 || + !a_and_b.result()->HasChild("Apple") || + !a_and_b.result()->HasChild("Banana")) { + LogMessage("ERROR: Query A-and-B returned unexpected results."); + failed = true; + } + if (c_only.error() != firebase::database::kErrorNone || + c_only.result()->children_count() != 1 || + !c_only.result()->HasChild("Cranberry")) { + LogMessage("ERROR: Query C-only returned unexpected results."); + failed = true; + } + if (!failed) { + LogMessage("SUCCESS: Query filtering succeeded."); + } + } + + // Test a ValueListener, which sits on a Query and listens for changes in + // the value at that location. + { + LogMessage("TEST: ValueListener"); + SampleValueListener* listener = new SampleValueListener(); + 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"); + + LogMessage(" Waiting for ValueListener..."); + + // Wait a few seconds for the value listener to be triggered. + ProcessEvents(2000); + + // Unregister the listener, so it stops triggering. + ref.Child("ValueListener").RemoveValueListener(listener); + + // Ensure that the listener is not triggered once removed. + WaitForCompletion(ref.Child("ValueListener").SetValue(4), "SetValueFour"); + + // Wait a few more seconds to ensure the listener is not triggered. + ProcessEvents(2000); + + // 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."); + } + + delete listener; + } + // Test a ChildListener, which sits on a Query and listens for changes in + // the child hierarchy at the location. + { + LogMessage("TEST: ChildListener"); + SampleChildListener* listener = new SampleChildListener(); + + // Set a child listener that only listens for entities of type "enemy". + auto entity_list = ref.Child("ChildListener").Child("entity_list"); + + entity_list.OrderByChild("entity_type") + .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"; + WaitForCompletion(entity_list.Child("0").SetValueAndPriority(params, 0), + "SetEntity0"); + params["entity_name"] = "warrior"; + params["entity_type"] = "hero"; + WaitForCompletion(entity_list.Child("1").SetValueAndPriority(params, 10), + "SetEntity1"); + params["entity_name"] = "wizard"; + params["entity_type"] = "hero"; + WaitForCompletion(entity_list.Child("2").SetValueAndPriority(params, 20), + "SetEntity2"); + params["entity_name"] = "rat"; + params["entity_type"] = "enemy"; + WaitForCompletion(entity_list.Child("3").SetValueAndPriority(params, 30), + "SetEntity3"); + params["entity_name"] = "thief"; + params["entity_type"] = "enemy"; + WaitForCompletion(entity_list.Child("4").SetValueAndPriority(params, 40), + "SetEntity4"); + params["entity_name"] = "paladin"; + params["entity_type"] = "hero"; + WaitForCompletion(entity_list.Child("5").SetValueAndPriority(params, 50), + "SetEntity5"); + params["entity_name"] = "ghost"; + params["entity_type"] = "enemy"; + WaitForCompletion(entity_list.Child("6").SetValueAndPriority(params, 60), + "SetEntity6"); + params["entity_name"] = "dragon"; + params["entity_type"] = "enemy"; + WaitForCompletion(entity_list.Child("7").SetValueAndPriority(params, 70), + "SetEntity7"); + // Now the thief becomes a hero! + WaitForCompletion( + entity_list.Child("4").Child("entity_type").SetValue("hero"), + "SetEntity4Type"); + // Now the dragon becomes a super-dragon! + WaitForCompletion( + entity_list.Child("7").Child("entity_name").SetValue("super-dragon"), + "SetEntity7Name"); + // Now the super-dragon becomes an mega-dragon! + WaitForCompletion( + entity_list.Child("7").Child("entity_name").SetValue("mega-dragon"), + "SetEntity7NameAgain"); + // And now we change a hero entity, which the Query ignores. + WaitForCompletion( + entity_list.Child("2").Child("entity_name").SetValue("super-wizard"), + "SetEntity2Value"); + // Now poof, the mega-dragon is gone. + WaitForCompletion(entity_list.Child("7").RemoveValue(), "RemoveEntity7"); + + LogMessage(" Waiting for ChildListener..."); + + // Wait a few seconds for the child listener to be triggered. + ProcessEvents(2000); + + // Unregister the listener, so it stops triggering. + entity_list.OrderByChild("entity_type") + .EqualTo("enemy") + .RemoveChildListener(listener); + + // Wait a few seconds for the child listener to be triggered. + ProcessEvents(2000); + + // Make one more change, to ensure the listener has been removed. + WaitForCompletion(entity_list.Child("6").SetPriority(0), + "SetEntity6Priority"); + + // We are expecting to have the following events: + bool failed = false; + if (listener->num_events("added 0") != 1) { + LogMessage( + "ERROR: OnChildAdded(0) was called an incorrect number of times."); + failed = true; + } + if (listener->num_events("added 3") != 1) { + LogMessage( + "ERROR: OnChildAdded(3) was called an incorrect number of times."); + failed = true; + } + if (listener->num_events("added 4") != 1) { + LogMessage( + "ERROR: OnChildAdded(4) was called an incorrect number of times."); + failed = true; + } + if (listener->num_events("added 6") != 1) { + LogMessage( + "ERROR: OnChildAdded(6) was called an incorrect number of times."); + failed = true; + } + if (listener->num_events("added 7") != 1) { + LogMessage( + "ERROR: OnChildAdded(7) was called an incorrect number of times."); + failed = true; + } + if (listener->num_events("removed 4") != 1) { + LogMessage( + "ERROR: OnChildRemoved(4) was called an incorrect number of " + "times."); + failed = true; + } + if (listener->num_events("changed 7") != 2) { + LogMessage( + "ERROR: OnChildChanged(7) was called an incorrect number of " + "times."); + failed = true; + } + if (listener->num_events("removed 7") != 1) { + LogMessage( + "ERROR: OnChildRemoved(7) was called an incorrect number of " + "times."); + failed = true; + } + if (listener->total_events() != 9) { + LogMessage("ERROR: ChildListener got an incorrect number of events."); + failed = true; + } + if (!failed) { + LogMessage("SUCCESS: ChildListener got all child events."); + } + delete listener; + } + + // Now check OnDisconnect. When you set an OnDisconnect handler for a + // database location, an operation will be performed on that location when + // you disconnect from Firebase Database. In this sample app, we replicate + // this by shutting down Firebase Database, then starting it up again and + // checking to see if the OnDisconnect actions were performed. + { + LogMessage("TEST: OnDisconnect"); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("SetValueTo1") + .OnDisconnect() + ->SetValue(1), + "OnDisconnectSetValue1"); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("SetValue2Priority3") + .OnDisconnect() + ->SetValueAndPriority(2, 3), + "OnDisconnectSetValue2Priority3"); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("SetValueButThenCancel") + .OnDisconnect() + ->SetValue("Going to cancel this"), + "OnDisconnectSetValueToCancel"); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("SetValueButThenCancel") + .OnDisconnect() + ->Cancel(), + "OnDisconnectCancel"); + // Set a value that we will then remove on disconnect. + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("RemoveValue") + .SetValue("Will be removed"), + "SetValueToRemove"); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("RemoveValue") + .OnDisconnect() + ->RemoveValue(), + "OnDisconnectRemoveValue"); + // Set up a map to pass to OnDisconnect()->UpdateChildren(). + std::map children; + children.insert(std::make_pair("one", 1)); + children.insert(std::make_pair("two", 2)); + children.insert(std::make_pair("three", 3)); + WaitForCompletion(ref.Child("OnDisconnectTests") + .Child("UpdateChildren") + .OnDisconnect() + ->UpdateChildren(children), + "OnDisconnectUpdateChildren"); + LogMessage(" Disconnection handlers registered."); + } + + // Go offline, wait a moment, then go online again. We set up a + // ValueListener + // on one of the OnDisconnect locations we set above, so we can see when the + // disconnection triggers. + { + ExpectValueListener* listener = new ExpectValueListener(1); + ref.Child("OnDisconnectTests") + .Child("SetValueTo1") + .AddValueListener(listener); + + LogMessage(" Disconnecting from Firebase Database."); + database->GoOffline(); + + while (!listener->got_value()) { + ProcessEvents(100); + } + ref.Child("OnDisconnectTests") + .Child("SetValueTo1") + .RemoveValueListener(listener); + delete listener; + listener = nullptr; + + LogMessage(" Reconnecting to Firebase Database."); + database->GoOnline(); + } + + /// Check that the DisconnectionHandler actions were performed. + /// Get a brand new reference to the location to be sure. + ref = database->GetReferenceFromUrl(saved_url.c_str()); + + firebase::Future future = + 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") || + result.Child("SetValueTo1").value().AsInt64().int64_value() != 1) { + LogMessage("ERROR: OnDisconnect.SetValue(1) failed."); + failed = true; + } + if (!result.HasChild("SetValue2Priority3") || + result.Child("SetValue2Priority3").value().AsInt64().int64_value() != + 2 || + result.Child("SetValue2Priority3").priority().AsInt64().int64_value() != + 3) { + LogMessage("ERROR: OnDisconnect.SetValueAndPriority(2, 3) failed."); + failed = true; + } + if (result.HasChild("RemoveValue")) { + LogMessage("ERROR: OnDisconnect.RemoveValue() failed."); + failed = true; + } + if (result.HasChild("SetValueButThenCancel")) { + LogMessage("ERROR: OnDisconnect.Cancel() failed."); + failed = true; + } + if (!result.HasChild("UpdateChildren") || + !result.Child("UpdateChildren").HasChild("one") || + result.Child("UpdateChildren") + .Child("one") + .value() + .AsInt64() + .int64_value() != 1 || + !result.Child("UpdateChildren").HasChild("two") || + result.Child("UpdateChildren") + .Child("two") + .value() + .AsInt64() + .int64_value() != 2 || + !result.Child("UpdateChildren").HasChild("three") || + result.Child("UpdateChildren") + .Child("three") + .value() + .AsInt64() + .int64_value() != 3) { + LogMessage("ERROR: OnDisconnect.UpdateChildren() failed."); + failed = true; + } + + if (!failed) { + LogMessage("SUCCESS: OnDisconnect values were written properly."); + } + } else { + LogMessage("ERROR: Couldn't read OnDisconnect changes, error %d: %s.", + 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."); + delete auth; + auth = nullptr; + + LogMessage("Shutdown 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/database/testapp/src/desktop/desktop_main.cc similarity index 52% rename from invites/testapp/src/desktop/desktop_main.cc rename to database/testapp/src/desktop/desktop_main.cc index 00e57132..0220c688 100644 --- a/invites/testapp/src/desktop/desktop_main.cc +++ b/database/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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/admob/testapp/src/ios/ios_main.mm b/database/testapp/src/ios/ios_main.mm old mode 100644 new mode 100755 similarity index 98% rename from admob/testapp/src/ios/ios_main.mm rename to database/testapp/src/ios/ios_main.mm index 6ccb2de5..2adcac9c --- a/admob/testapp/src/ios/ios_main.mm +++ b/database/testapp/src/ios/ios_main.mm @@ -101,6 +101,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/database/testapp/src/main.h similarity index 100% rename from admob/testapp/src/main.h rename to database/testapp/src/main.h diff --git a/invites/testapp/testapp.xcodeproj/project.pbxproj b/database/testapp/testapp.xcodeproj/project.pbxproj similarity index 99% rename from invites/testapp/testapp.xcodeproj/project.pbxproj rename to database/testapp/testapp.xcodeproj/project.pbxproj index baf45c4c..5769362c 100644 --- a/invites/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/invites/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/database/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from invites/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json rename to database/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json diff --git a/invites/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/database/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json similarity index 100% rename from invites/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json rename to database/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json diff --git a/database/testapp/testapp/Info.plist b/database/testapp/testapp/Info.plist new file mode 100644 index 00000000..095e6672 --- /dev/null +++ b/database/testapp/testapp/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.database.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/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/invites/testapp/LaunchScreen.storyboard b/firestore/testapp/LaunchScreen.storyboard similarity index 100% rename from invites/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/invites/testapp/gradle/wrapper/gradle-wrapper.jar b/firestore/testapp/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from invites/testapp/gradle/wrapper/gradle-wrapper.jar rename to firestore/testapp/gradle/wrapper/gradle-wrapper.jar diff --git a/admob/testapp/gradle/wrapper/gradle-wrapper.properties b/firestore/testapp/gradle/wrapper/gradle-wrapper.properties similarity index 52% rename from admob/testapp/gradle/wrapper/gradle-wrapper.properties rename to firestore/testapp/gradle/wrapper/gradle-wrapper.properties index d5705170..65340c1b 100644 --- a/admob/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/invites/testapp/gradlew b/firestore/testapp/gradlew similarity index 100% rename from invites/testapp/gradlew rename to firestore/testapp/gradlew diff --git a/invites/testapp/gradlew.bat b/firestore/testapp/gradlew.bat similarity index 100% rename from invites/testapp/gradlew.bat rename to firestore/testapp/gradlew.bat diff --git a/firestore/testapp/proguard.pro b/firestore/testapp/proguard.pro new file mode 100644 index 00000000..54cd248b --- /dev/null +++ b/firestore/testapp/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; } diff --git a/firestore/testapp/res/layout/main.xml b/firestore/testapp/res/layout/main.xml new file mode 100644 index 00000000..d3ffb630 --- /dev/null +++ b/firestore/testapp/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/firestore/testapp/res/values/strings.xml b/firestore/testapp/res/values/strings.xml new file mode 100644 index 00000000..25c93dde --- /dev/null +++ b/firestore/testapp/res/values/strings.xml @@ -0,0 +1,4 @@ + + + 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/invites/testapp/src/android/android_main.cc b/firestore/testapp/src/android/android_main.cc similarity index 100% rename from invites/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 98% 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 index acbd8d3e..11d67c5b 100644 --- a/admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java +++ b/firestore/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(); 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/firestore/testapp/src/desktop/desktop_main.cc b/firestore/testapp/src/desktop/desktop_main.cc new file mode 100644 index 00000000..3a027f82 --- /dev/null +++ b/firestore/testapp/src/desktop/desktop_main.cc @@ -0,0 +1,123 @@ +// 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 * 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/invites/testapp/src/main.h b/firestore/testapp/src/main.h similarity index 100% rename from invites/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/firestore/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/firestore/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 00000000..6f870a46 --- /dev/null +++ b/firestore/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/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/functions/testapp/AndroidManifest.xml b/functions/testapp/AndroidManifest.xml new file mode 100644 index 00000000..0b38246d --- /dev/null +++ b/functions/testapp/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + 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/invites/testapp/LICENSE b/functions/testapp/LICENSE similarity index 99% rename from invites/testapp/LICENSE rename to functions/testapp/LICENSE index 25dd9993..d6456956 100644 --- a/invites/testapp/LICENSE +++ b/functions/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/functions/testapp/LaunchScreen.storyboard b/functions/testapp/LaunchScreen.storyboard new file mode 100644 index 00000000..673e0f7e --- /dev/null +++ b/functions/testapp/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + 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/functions/testapp/gradle/wrapper/gradle-wrapper.jar b/functions/testapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/functions/testapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/functions/testapp/gradle/wrapper/gradle-wrapper.properties b/functions/testapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..65340c1b --- /dev/null +++ b/functions/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/functions/testapp/gradlew b/functions/testapp/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/functions/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/functions/testapp/gradlew.bat b/functions/testapp/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/functions/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/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/functions/testapp/src/android/android_main.cc b/functions/testapp/src/android/android_main.cc new file mode 100644 index 00000000..4b86ce30 --- /dev/null +++ b/functions/testapp/src/android/android_main.cc @@ -0,0 +1,261 @@ +// 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; +} + +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; } + +// 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/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/admob/testapp/src/desktop/desktop_main.cc b/functions/testapp/src/desktop/desktop_main.cc similarity index 52% rename from admob/testapp/src/desktop/desktop_main.cc rename to functions/testapp/src/desktop/desktop_main.cc index 00e57132..0220c688 100644 --- a/admob/testapp/src/desktop/desktop_main.cc +++ b/functions/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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/functions/testapp/src/main.h b/functions/testapp/src/main.h new file mode 100644 index 00000000..f5980902 --- /dev/null +++ b/functions/testapp/src/main.h @@ -0,0 +1,67 @@ +// 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 + +#include +#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); + +// 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__) +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/functions/testapp/testapp.xcodeproj/project.pbxproj b/functions/testapp/testapp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..5769362c --- /dev/null +++ b/functions/testapp/testapp.xcodeproj/project.pbxproj @@ -0,0 +1,312 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + 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 */; }; + 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 = ""; }; + 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 = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 52B71EBA1C8600B600398745 /* Images.xcassets */, + 52FD1FF81C85FFA000BC68E3 /* Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* testapp.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* testapp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "testapp" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* 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 = ( + 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 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; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + 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; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + 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/functions/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/functions/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..b7f3352e --- /dev/null +++ b/functions/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "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" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file 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/gma/testapp/LICENSE b/gma/testapp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/gma/testapp/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + 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. + 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/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/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java new file mode 100644 index 00000000..3cb37ecf --- /dev/null +++ b/gma/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -0,0 +1,55 @@ +// 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/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/gma/testapp/src/ios/ios_main.mm b/gma/testapp/src/ios/ios_main.mm new file mode 100644 index 00000000..4c226d5e --- /dev/null +++ b/gma/testapp/src/ios/ios_main.mm @@ -0,0 +1,121 @@ +// 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; +static NSCondition *g_shutdown_signal; +static UITextView *g_text_view; +static UIView *g_parent_view; + +@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; +} + +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.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/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 0d594c95..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.0.0' - 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.2' - - 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 fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.firebase:firebase-invites:9.0.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 196ec7fc..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=4.9 -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 9a378fea..00000000 --- a/invites/testapp/readme.md +++ /dev/null @@ -1,165 +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". - - 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 e5df429c..00000000 --- a/invites/testapp/src/common_main.cc +++ /dev/null @@ -1,158 +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" - -// Thin OS abstraction layer. -#include "main.h" // NOLINT - -// Execute all methods of the C++ Invites API. -extern "C" int common_main(int argc, const char* argv[]) { - ::firebase::App* app; - ::firebase::invites::InvitesSender* sender; - ::firebase::invites::InvitesReceiver* receiver; - - LogMessage("Initializing Firebase App"); - -#if defined(__ANDROID__) - app = ::firebase::App::Create(::firebase::AppOptions(), GetJniEnv(), - GetActivity()); -#else - app = ::firebase::App::Create(::firebase::AppOptions()); -#endif // defined(__ANDROID__) - - LogMessage("Created the Firebase App %x", - static_cast(reinterpret_cast(app))); - - LogMessage("Creating an InvitesReceiver"); - receiver = new firebase::invites::InvitesReceiver(*app); - - LogMessage("Creating an InvitesSender"); - sender = new firebase::invites::InvitesSender(*app); - - bool testing_conversion = false; - { - // Check for received invitations. - LogMessage("Fetch: Fetching invites..."); - auto future_result = receiver->Fetch(); - while (future_result.Status() == firebase::kFutureStatusPending) { - if (ProcessEvents(10)) break; - } - - if (future_result.Status() == firebase::kFutureStatusInvalid) { - LogMessage("Fetch: Invalid, sorry!"); - } else if (future_result.Status() == firebase::kFutureStatusComplete) { - LogMessage("Fetch: Complete!"); - if (future_result.Error() != 0) { - LogMessage("Fetch: Error %d: %s", future_result.Error(), - future_result.ErrorMessage()); - } else { - auto result = *future_result.Result(); - // error_code == 0 - if (result.invitation_id != "") { - LogMessage("Fetch: Got invitation ID: %s", - result.invitation_id.c_str()); - - // We got an invitation ID, so let's try and convert it. - LogMessage("ConvertInvitation: Converting invitation %s", - result.invitation_id.c_str()); - - receiver->ConvertInvitation(result.invitation_id.c_str()); - testing_conversion = true; - } - if (result.deep_link != "") { - LogMessage("Fetch: Got deep link: %s", result.deep_link.c_str()); - } - if (result.invitation_id == "" && result.deep_link == "") { - LogMessage("Fetch: No invitation ID or deep link, confirmed."); - } - } - } - } - - // Check if we are performing a conversion. - if (testing_conversion) { - auto future_result = receiver->ConvertInvitationLastResult(); - while (future_result.Status() == firebase::kFutureStatusPending) { - if (ProcessEvents(10)) break; - } - - 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.ErrorMessage()); - } else { - auto result = *future_result.Result(); - LogMessage( - "ConvertInvitation: Successfully converted invitation ID: %s", - result.invitation_id.c_str()); - } - } - } - - { - // Now try sending an invitation. - LogMessage("SendInvite: Sending an invitation..."); - sender->SetTitleText("Invites Test App"); - sender->SetMessageText("Please try my app! It's awesome."); - sender->SetCallToActionText("Download it for FREE"); - sender->SetDeepLinkUrl("http://google.com/abc"); - - auto future_result = sender->SendInvite(); - 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.ErrorMessage()); - } 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()); - } - } - } - } - } - LogMessage("Sample finished."); - - while (!ProcessEvents(1000)) { - } - - delete sender; - sender = nullptr; - delete receiver; - receiver = nullptr; - 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 c86c4546..00000000 --- a/invites/testapp/testapp/Info.plist +++ /dev/null @@ -1,55 +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 - - diff --git a/messaging/testapp/AndroidManifest.xml b/messaging/testapp/AndroidManifest.xml index 52641582..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"> - + @@ -18,13 +18,28 @@ - + + + + + + + + + + - - - - - - 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 76ee4516..4f607fe3 100644 --- a/messaging/testapp/build.gradle +++ b/messaging/testapp/build.gradle @@ -2,121 +2,75 @@ buildscript { repositories { mavenLocal() + maven { url 'https://maven.google.com' } jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' - 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.2' + 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') } + } + packagingOptions { + pickFirst 'META-INF/**/coroutines.pro' + } + lintOptions { + abortOnError false + checkReleaseBuilds false + } } -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.firebase:firebase-messaging:9.0.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) -} - -dependencies { - compile files(new File(firebase_cpp_sdk_dir, 'libs/android/libmessaging_java.jar')) -} 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 196ec7fc..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=4.9 -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 5048bb9c..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 `Notifications` in the menu on the left - - Select the `Credentials` 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 e95e1259..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,8 @@ import android.app.NativeActivity; import android.content.Intent; +import android.os.Bundle; +import com.google.firebase.messaging.MessageForwardingService; /** * TestappNativeActivity is a NativeActivity that updates its intent when new intents are sent to @@ -14,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. * @@ -22,11 +36,26 @@ public class TestappNativeActivity extends NativeActivity { * message with both a data and notification payload is receieved the data payload is stored on * the notification Intent. NativeActivity does not provide native callbacks for onNewIntent, so * it cannot route the data payload that is stored in the Intent to the C++ function OnMessage. As - * a workaround, we override onNewIntent so that it sets the Activity's intent, allowing the - * native C++ code to get access to the data payload in the Intent extras and call OnMessage. + * a workaround, we override onNewIntent so that it forwards the intent to the C++ library's + * service which in turn forwards the data to the native C++ messaging library. */ @Override protected void onNewIntent(Intent intent) { + // 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 c18e843f..45a4fca7 100644 --- a/messaging/testapp/src/common_main.cc +++ b/messaging/testapp/src/common_main.cc @@ -13,67 +13,181 @@ // 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"); - if (!message.from.empty()) LogMessage("from: %s", message.from.c_str()); - if (!message.data.empty()) { - LogMessage("data:"); - typedef std::map::const_iterator MapIter; - for (MapIter it = message.data.begin(); it != message.data.end(); ++it) { - LogMessage(" %s: %s", it->first.c_str(), it->second.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(::firebase::AppOptions(), GetJniEnv(), - GetActivity()); + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); #else - app = ::firebase::App::Create(::firebase::AppOptions()); + app = ::firebase::App::Create(); #endif // defined(__ANDROID__) LogMessage("Initialized Firebase App."); - ::firebase::messaging::Initialize(*app, &g_listener); + LogMessage("Initialize the Messaging library"); + + ::firebase::ModuleInitializer initializer; + 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 + } + if (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Firebase Messaging: %s", + initializer.InitializeLastResult().error_message()); + ProcessEvents(2000); + return 1; + } + LogMessage("Initialized Firebase Cloud Messaging."); - ::firebase::messaging::Subscribe("/topics/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 00e57132..0220c688 100644 --- a/messaging/testapp/src/desktop/desktop_main.cc +++ b/messaging/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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 baf45c4c..c5f566b4 100644 --- a/messaging/testapp/testapp.xcodeproj/project.pbxproj +++ b/messaging/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 */; }; + D6E5B5581E29779D00CC1BF8 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6E5B5571E29779D00CC1BF8 /* UserNotifications.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; /* 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 = ""; }; + D6E5B5571E29779D00CC1BF8 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -37,6 +39,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D6E5B5581E29779D00CC1BF8 /* UserNotifications.framework in Frameworks */, 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, @@ -70,6 +73,7 @@ 529226D41C85F68000C89379 /* Frameworks */ = { isa = PBXGroup; children = ( + D6E5B5571E29779D00CC1BF8 /* UserNotifications.framework */, 529226D51C85F68000C89379 /* Foundation.framework */, 529226D71C85F68000C89379 /* CoreGraphics.framework */, 529226D91C85F68000C89379 /* UIKit.framework */, @@ -208,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; @@ -245,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 bfc75822..a7a904df 100644 --- a/remote_config/testapp/AndroidManifest.xml +++ b/remote_config/testapp/AndroidManifest.xml @@ -6,9 +6,12 @@ - + - + diff --git a/remote_config/testapp/CMakeLists.txt b/remote_config/testapp/CMakeLists.txt new file mode 100644 index 00000000..b83676fe --- /dev/null +++ b/remote_config/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_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 b3ec5f1b..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.0.0' - 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.2' + 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 fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.firebase:firebase-config:9.0.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 196ec7fc..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=4.9 -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 1b93d17d..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. @@ -121,11 +165,13 @@ which are set by the call to `SetDefaults()` - The app then fetches those parameters from the Firebase Console, and prints out the values again. - Note that if new values are not set, the same default values are printed. +- The app also prints all keys associated with data after the fetch, and then +keys that begin with "TestD". Support ------- -[https://firebase.google.com/support/]() +[https://firebase.google.com/support/](https://firebase.google.com/support/) License ------- @@ -146,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 2ba919f3..3bc35e2c 100644 --- a/remote_config/testapp/src/common_main.cc +++ b/remote_config/testapp/src/common_main.cc @@ -16,113 +16,222 @@ #include "firebase/app.h" #include "firebase/remote_config.h" +#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(::firebase::AppOptions(), GetJniEnv(), - GetActivity()); + app = ::firebase::App::Create(GetJniEnv(), GetActivity()); #else - app = ::firebase::App::Create(::firebase::AppOptions()); -#endif // defined(__ANDROID__) + app = ::firebase::App::Create(); +#endif // defined(__ANDROID__) LogMessage("Created the Firebase app %x", static_cast(reinterpret_cast(app))); - remote_config::Initialize(*app); + + ::firebase::ModuleInitializer initializer; + + 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 (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Firebase Remote Config: %s", + initializer.InitializeLastResult().error_message()); + ProcessEvents(2000); + return 1; + } + LogMessage("Initialized the Firebase Remote Config API"); - static const remote_config::ConfigKeyValue defaults[] = { + // Initialization Complete + // Set Defaults, and test them + static const unsigned char kBinaryDefaults[] = {6, 0, 0, 6, 7, 3}; + + static const remote_config::ConfigKeyValueVariant defaults[] = { {"TestBoolean", "True"}, - {"TestLong", "42"}, - {"TestDouble", "3.14"}, + {"TestLong", 42}, + {"TestDouble", 3.14}, {"TestString", "Hello World"}, - {"TestData", "abcde"}}; + {"TestData", firebase::Variant::FromStaticBlob(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 (%c)", i, value, value); + 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"); - assert(*remote_config::GetConfigSetting( - remote_config::kConfigSettingDeveloperMode) - .c_str() == '1'); + // 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); - while (future_result.Status() == firebase::kFutureStatusPending) { + auto future_result = rc_->Fetch(0); + while (future_result.status() == firebase::kFutureStatusPending) { if (ProcessEvents(1000)) { break; } } - if (future_result.Status() == firebase::kFutureStatusComplete) { + 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; + } + } + + bool activate_result = activate_future_result.result(); + LogMessage("Activate %s", activate_result ? "succeeded" : "failed"); - 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); + 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 (%c)", i, value, value); + 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)); + } + { + // 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 @@ -133,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 00e57132..0220c688 100644 --- a/remote_config/testapp/src/desktop/desktop_main.cc +++ b/remote_config/testapp/src/desktop/desktop_main.cc @@ -15,14 +15,35 @@ #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; @@ -48,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); @@ -59,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 @@ -67,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/admob/testapp/AndroidManifest.xml b/storage/testapp/AndroidManifest.xml similarity index 76% rename from admob/testapp/AndroidManifest.xml rename to storage/testapp/AndroidManifest.xml index e5da1f5d..253ce8c0 100644 --- a/admob/testapp/AndroidManifest.xml +++ b/storage/testapp/AndroidManifest.xml @@ -1,15 +1,17 @@ - + + android:exported = "true" + android:screenOrientation="portrait" + android:configChanges="orientation|screenSize"> diff --git a/storage/testapp/AndroidManifestAutomated.xml b/storage/testapp/AndroidManifestAutomated.xml new file mode 100644 index 00000000..4c9961da --- /dev/null +++ b/storage/testapp/AndroidManifestAutomated.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + 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/LICENSE b/storage/testapp/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/storage/testapp/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + 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. + 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/storage/testapp/LaunchScreen.storyboard b/storage/testapp/LaunchScreen.storyboard new file mode 100644 index 00000000..673e0f7e --- /dev/null +++ b/storage/testapp/LaunchScreen.storyboard @@ -0,0 +1,7 @@ + + + + + + + diff --git a/storage/testapp/Podfile b/storage/testapp/Podfile new file mode 100644 index 00000000..7010e7c0 --- /dev/null +++ b/storage/testapp/Podfile @@ -0,0 +1,8 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '13.0' +use_frameworks! +# Cloud Storage for Firebase test application. +target 'testapp' do + 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 new file mode 100644 index 00000000..aa20418d --- /dev/null +++ b/storage/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.storage.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 + storage +} + +apply plugin: 'com.google.gms.google-services' 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.jar b/storage/testapp/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/storage/testapp/gradle/wrapper/gradle-wrapper.jar differ diff --git a/storage/testapp/gradle/wrapper/gradle-wrapper.properties b/storage/testapp/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..65340c1b --- /dev/null +++ b/storage/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/storage/testapp/gradlew b/storage/testapp/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/storage/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/storage/testapp/gradlew.bat b/storage/testapp/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/storage/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/storage/testapp/proguard.pro b/storage/testapp/proguard.pro new file mode 100644 index 00000000..2d04b8a9 --- /dev/null +++ b/storage/testapp/proguard.pro @@ -0,0 +1,2 @@ +-ignorewarnings +-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { * ; } diff --git a/storage/testapp/readme.md b/storage/testapp/readme.md new file mode 100644 index 00000000..96cdcaae --- /dev/null +++ b/storage/testapp/readme.md @@ -0,0 +1,210 @@ +Cloud Storage for Firebase Quickstart +======================== + +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 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 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 + current time in microseconds to work in, and gets a reference to that child, + which the testapp will use for the remainder of its actions. + - Uploads some sample files and reads them back to ensure the storage can be + read from and written to. + - Checks the Metadata of the uploaded and downloaded files to ensure they + 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 Cloud Storage, Firebase Auth, and Firebase App systems. + +Introduction +------------ + +- [Read more about Cloud Storage for Firebase](https://firebase.google.com/docs/storage/) + +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 the [Firebase console](https://firebase.google.com/console/), and attach + your iOS app to it. + - You can use "com.google.firebase.cpp.storage.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 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](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_storage.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.storage.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 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](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 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/storage/testapp/res/layout/main.xml b/storage/testapp/res/layout/main.xml new file mode 100644 index 00000000..d3ffb630 --- /dev/null +++ b/storage/testapp/res/layout/main.xml @@ -0,0 +1,12 @@ + + + + diff --git a/storage/testapp/res/values/strings.xml b/storage/testapp/res/values/strings.xml new file mode 100644 index 00000000..eec5f22d --- /dev/null +++ b/storage/testapp/res/values/strings.xml @@ -0,0 +1,4 @@ + + + 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 new file mode 100644 index 00000000..d8e4d4dc --- /dev/null +++ b/storage/testapp/src/android/android_main.cc @@ -0,0 +1,375 @@ +// 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 +#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; +} + +namespace app_framework { + +// 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; +} + +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; } + +// 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), + logging_utils_get_did_touch_(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"); + logging_utils_get_did_touch_ = + env->GetStaticMethodID(logging_utils_class_, "getDidTouch", "()Z"); + + 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); + } + + 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; + +// 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, TESTAPP_NAME, + "-------------------JNI exception:"); + __android_log_print(ANDROID_LOG_INFO, TESTAPP_NAME, "%s", exception_text); + __android_log_print(ANDROID_LOG_INFO, 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); + } +} + +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 "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, 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(); +} + +// 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; +} + +// 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 + // 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. + 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[] = {TESTAPP_NAME}; + int return_value = common_main(1, argv); + (void)return_value; // Ignore the return value. + + // 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 app_framework::g_logging_utils_data; + app_framework::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/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java new file mode 100644 index 00000000..a49996a6 --- /dev/null +++ b/storage/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java @@ -0,0 +1,101 @@ +// 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.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; +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 { + 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 = 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); + + // 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 (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 new file mode 100644 index 00000000..a333c4fb --- /dev/null +++ b/storage/testapp/src/common_main.cc @@ -0,0 +1,231 @@ +// 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/app.h" +#include "firebase/auth.h" +#include "firebase/future.h" +#include "firebase/log.h" +#include "firebase/storage.h" +#include "firebase/util.h" + +// 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"; + +// 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. +void WaitForCompletion(const firebase::FutureBase& future, const char* name) { + while (future.status() == firebase::kFutureStatusPending) { + ProcessEvents(100); + } + if (future.status() != firebase::kFutureStatusComplete) { + LogMessage("ERROR: %s returned an invalid result.", name); + } else if (future.error() != 0) { + LogMessage("ERROR: %s returned error %d: %s", name, future.error(), + future.error_message()); + } +} + +extern "C" int common_main(int argc, const char* argv[]) { + ::firebase::App* app; + +#if defined(__ANDROID__) + 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 Cloud Storage."); + + // Use ModuleInitializer to initialize both Auth and Storage, ensuring no + // dependencies are missing. + ::firebase::storage::Storage* storage = nullptr; + ::firebase::auth::Auth* auth = nullptr; + void* initialize_targets[] = {&auth, &storage}; + + 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 Storage."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult 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; + }}; + + ::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 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 = + 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 Storage anyway. This may fail " + "depending on the security settings."); + } + } + + // Generate a folder for the test data based on the time in milliseconds. + 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 in. + firebase::storage::StorageReference ref; + ref = storage->GetReference("test_app_data").Child(saved_url); + + 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 + // back from the storage to confirm that it was uploaded. Then it will remove + // the file. + { + const std::string kSimpleTestFile = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " + "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum."; + { + 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("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."); + } + } + } + + { + 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 (memcmp(&kSimpleTestFile[0], buffer, kSimpleTestFile.size()) != 0) { + LogMessage("ERROR: file contents did not match."); + } + } + } + { + LogMessage("Delete the sample file."); + + firebase::Future future = + ref.Child("TestFile").Child("SampleFile.txt").Delete(); + WaitForCompletion(future, "Delete"); + } + } + + LogMessage("Shutdown the Storage library."); + delete storage; + storage = nullptr; + + 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)) { + } + + return 0; +} diff --git a/storage/testapp/src/desktop/desktop_main.cc b/storage/testapp/src/desktop/desktop_main.cc new file mode 100644 index 00000000..7f3a95ae --- /dev/null +++ b/storage/testapp/src/desktop/desktop_main.cc @@ -0,0 +1,171 @@ +// 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 // NOLINT + +#ifdef _WIN32 +#include +#define chdir _chdir +#else +#include +#include +#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 + +namespace app_framework { + +bool ProcessEvents(int msec) { +#ifdef _WIN32 + Sleep(msec); +#else + usleep(msec * 1000); +#endif // _WIN32 + return quit; +} + +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); + 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 + signal(SIGINT, SignalHandler); +#endif // _WIN32 + return common_main(argc, argv); +} diff --git a/storage/testapp/src/ios/ios_main.mm b/storage/testapp/src/ios/ios_main.mm new file mode 100755 index 00000000..61a8bc36 --- /dev/null +++ b/storage/testapp/src/ios/ios_main.mm @@ -0,0 +1,201 @@ +// 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 + +#include +#include +#include +#include +#include +#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; +static NSCondition *g_shutdown_signal; +static UITextView *g_text_view; +static UIView *g_parent_view; + +@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[] = {TESTAPP_NAME}; + [g_shutdown_signal lock]; + g_exit_status = common_main(1, argv); + [g_shutdown_complete signal]; + }); +} + +@end +namespace app_framework { + +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; +} + +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 LogMessageV(const char *format, va_list list) { + NSString *formatString = @(format); + + 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; +} + +@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; + g_text_view.font = [UIFont fontWithName:@"Courier" size:10]; + [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/storage/testapp/src/main.h b/storage/testapp/src/main.h new file mode 100644 index 00000000..2d30c14e --- /dev/null +++ b/storage/testapp/src/main.h @@ -0,0 +1,96 @@ +// 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 + +#include + +#include +#if !defined(_WIN32) +#include +#endif +#if defined(__ANDROID__) +#include +#include +#elif defined(__APPLE__) +extern "C" { +#include +} // extern "C" +#endif // __ANDROID__ + +// Defined using -DTESTAPP_NAME=some_app_name when compiling this +// file. +#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. +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. +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__) +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(); + +// 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 new file mode 100644 index 00000000..5769362c --- /dev/null +++ b/storage/testapp/testapp.xcodeproj/project.pbxproj @@ -0,0 +1,312 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 520BC0391C869159008CFBC3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */; }; + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D51C85F68000C89379 /* Foundation.framework */; }; + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D71C85F68000C89379 /* CoreGraphics.framework */; }; + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 529226D91C85F68000C89379 /* UIKit.framework */; }; + 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 */; }; + 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 = ""; }; + 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 = ( + 529226D81C85F68000C89379 /* CoreGraphics.framework in Frameworks */, + 529226DA1C85F68000C89379 /* UIKit.framework in Frameworks */, + 529226D61C85F68000C89379 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 529226C91C85F68000C89379 = { + isa = PBXGroup; + children = ( + D66B16861CE46E8900E5638A /* LaunchScreen.storyboard */, + 520BC0381C869159008CFBC3 /* GoogleService-Info.plist */, + 52B71EBA1C8600B600398745 /* Images.xcassets */, + 52FD1FF81C85FFA000BC68E3 /* Info.plist */, + 5292271D1C85FB5500C89379 /* src */, + 529226D41C85F68000C89379 /* Frameworks */, + 529226D31C85F68000C89379 /* Products */, + ); + sourceTree = ""; + }; + 529226D31C85F68000C89379 /* Products */ = { + isa = PBXGroup; + children = ( + 529226D21C85F68000C89379 /* testapp.app */, + ); + name = Products; + sourceTree = ""; + }; + 529226D41C85F68000C89379 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 529226D51C85F68000C89379 /* Foundation.framework */, + 529226D71C85F68000C89379 /* CoreGraphics.framework */, + 529226D91C85F68000C89379 /* UIKit.framework */, + 529226EE1C85F68000C89379 /* XCTest.framework */, + ); + 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 = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 529226D11C85F68000C89379 /* testapp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 529226F91C85F68000C89379 /* Build configuration list for PBXNativeTarget "testapp" */; + buildPhases = ( + 529226CE1C85F68000C89379 /* Sources */, + 529226CF1C85F68000C89379 /* Frameworks */, + 529226D01C85F68000C89379 /* 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 = ( + 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 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; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + 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; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + 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/storage/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/storage/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..b7f3352e --- /dev/null +++ b/storage/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "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" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/storage/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/storage/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json new file mode 100644 index 00000000..6f870a46 --- /dev/null +++ b/storage/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/storage/testapp/testapp/Info.plist b/storage/testapp/testapp/Info.plist new file mode 100644 index 00000000..31425994 --- /dev/null +++ b/storage/testapp/testapp/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.google.firebase.cpp.storage.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 + +