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/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644
index 00000000..e086d3f9
--- /dev/null
+++ b/.github/workflows/android.yml
@@ -0,0 +1,209 @@
+name: Android Builds
+
+on:
+ schedule:
+ - cron: "0 9 * * *" # 9am UTC = 1am PST / 2am PDT. for all testapps except firestore
+ pull_request:
+ types: [opened, reopened, synchronize]
+ workflow_dispatch:
+ inputs:
+ apis:
+ description: 'CSV of apis to build and test'
+ default: 'analytics,auth,database,dynamic_links,firestore,functions,gma,messaging,remote_config,storage'
+ required: true
+
+env:
+ CCACHE_DIR: ${{ github.workspace }}/ccache_dir
+ GITHUB_TOKEN: ${{ github.token }}
+ xcodeVersion: "13.3.1" # Only affects Mac runners, and only for prerequisites.
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ check_and_prepare:
+ runs-on: ubuntu-latest
+ outputs:
+ apis: ${{ steps.set_outputs.outputs.apis }}
+ steps:
+ - id: set_outputs
+ run: |
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ apis="${{ github.event.inputs.apis }}"
+ else
+ apis="analytics,auth,database,dynamic_links,firestore,functions,gma,messaging,remote_config,storage"
+ fi
+ echo apis: ${apis}
+ echo "::set-output name=apis::${apis}"
+
+ build:
+ name: android-${{ matrix.os }}-${{ matrix.architecture }}-${{ matrix.python_version }}
+ runs-on: ${{ matrix.os }}
+ needs: [check_and_prepare]
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-12, ubuntu-latest, windows-latest]
+ architecture: [x64]
+ python_version: [3.7]
+ steps:
+ - name: setup Xcode version (macos)
+ if: runner.os == 'macOS'
+ run: sudo xcode-select -s /Applications/Xcode_${{ env.xcodeVersion }}.app/Contents/Developer
+
+ - name: Store git credentials for all git commands
+ # Forces all git commands to use authenticated https, to prevent throttling.
+ shell: bash
+ run: |
+ git config --global credential.helper 'store --file /tmp/git-credentials'
+ echo 'https://${{ github.token }}@github.com' > /tmp/git-credentials
+
+ - name: Enable Git Long-paths Support
+ if: runner.os == 'Windows'
+ run: git config --system core.longpaths true
+
+ - uses: actions/checkout@v2
+ with:
+ submodules: true
+
+ - name: Set env variables for subsequent steps (all)
+ shell: bash
+ run: |
+ echo "MATRIX_UNIQUE_NAME=${{ matrix.os }}-${{ matrix.architecture }}" >> $GITHUB_ENV
+ echo "GHA_INSTALL_CCACHE=1" >> $GITHUB_ENV
+
+ - name: Setup python
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python_version }}
+ architecture: ${{ matrix.architecture }}
+
+ - name: Add msbuild to PATH
+ if: startsWith(matrix.os, 'windows')
+ uses: microsoft/setup-msbuild@v1.0.2
+
+ - name: Cache NDK
+ id: cache_ndk
+ uses: actions/cache@v2
+ with:
+ path: /tmp/android-ndk-r21e
+ key: android-ndk-${{ matrix.os }}-r21e
+
+ - name: Check cached NDK
+ shell: bash
+ if: steps.cache_ndk.outputs.cache-hit != 'true'
+ run: |
+ # If the NDK failed to download from the cache, but there is a
+ # /tmp/android-ndk-r21e directory, it's incomplete, so remove it.
+ if [[ -d "/tmp/android-ndk-r21e" ]]; then
+ echo "Removing incomplete download of NDK"
+ rm -rf /tmp/android-ndk-r21e
+ fi
+
+ - name: Update homebrew (avoid bintray errors)
+ uses: nick-invision/retry@v2
+ if: startsWith(matrix.os, 'macos')
+ with:
+ timeout_minutes: 10
+ max_attempts: 3
+ command: |
+ # Temporarily here until Github runners have updated their version of
+ # homebrew. This prevents errors arising from the shut down of
+ # binutils, used by older version of homebrew for hosting packages.
+ brew update
+
+ - name: Install prerequisites
+ uses: nick-invision/retry@v2
+ with:
+ timeout_minutes: 10
+ max_attempts: 3
+ shell: bash
+ command: |
+ scripts/build_scripts/android/install_prereqs.sh
+ echo "NDK_ROOT=/tmp/android-ndk-r21e" >> $GITHUB_ENV
+ echo "ANDROID_NDK_HOME=/tmp/android-ndk-r21e" >> $GITHUB_ENV
+ pip install -r scripts/build_scripts/python_requirements.txt
+ python scripts/restore_secrets.py --passphrase "${{ secrets.TEST_SECRET }}"
+
+ - name: Download C++ SDK
+ shell: bash
+ run: |
+ set +e
+ # Retry up to 10 times because Curl has a tendency to timeout on
+ # Github runners.
+ for retry in {1..10} error; do
+ if [[ $retry == "error" ]]; then exit 5; fi
+ curl -LSs \
+ "https://firebase.google.com/download/cpp" \
+ --output firebase_cpp_sdk.zip && break
+ sleep 300
+ done
+ set -e
+ mkdir /tmp/downloaded_sdk
+ unzip -q firebase_cpp_sdk.zip -d /tmp/downloaded_sdk/
+ rm -f firebase_cpp_sdk.zip
+
+ - name: Cache ccache files
+ id: cache_ccache
+ uses: actions/cache@v2
+ with:
+ path: ccache_dir
+ key: dev-test-ccache-${{ env.MATRIX_UNIQUE_NAME }}
+
+ - name: Build testapp
+ shell: bash
+ run: |
+ set -x
+ python scripts/build_scripts/build_testapps.py --p Android \
+ --t ${{ needs.check_and_prepare.outputs.apis }} \
+ --output_directory "${{ github.workspace }}" \
+ --artifact_name "android-${{ matrix.os }}" \
+ --noadd_timestamp \
+ --short_output_paths \
+ --gha_build \
+ --packaged_sdk /tmp/downloaded_sdk/firebase_cpp_sdk
+
+ - name: Stats for ccache (mac and linux)
+ if: startsWith(matrix.os, 'ubuntu') || startsWith(matrix.os, 'macos')
+ run: ccache -s
+
+ - name: Prepare results summary artifact
+ if: ${{ !cancelled() }}
+ shell: bash
+ run: |
+ if [ ! -f build-results-android-${{ matrix.os }}.log.json ]; then
+ echo "__SUMMARY_MISSING__" > build-results-android-${{ matrix.os }}.log.json
+ fi
+
+ - name: Upload Android integration tests artifact
+ uses: actions/upload-artifact@v3
+ if: ${{ !cancelled() }}
+ with:
+ name: testapps-android-${{ matrix.os }}
+ path: testapps-android-${{ matrix.os }}
+ retention-days: ${{ env.artifactRetentionDays }}
+
+ - name: Upload Android build results artifact
+ uses: actions/upload-artifact@v3
+ if: ${{ !cancelled() }}
+ with:
+ name: log-artifact
+ path: build-results-android-${{ matrix.os }}*
+ retention-days: ${{ env.artifactRetentionDays }}
+
+ - name: Download log artifacts
+ if: ${{ needs.check_and_prepare.outputs.pr_number && failure() && !cancelled() }}
+ uses: actions/download-artifact@v3
+ with:
+ path: test_results
+ name: log-artifact
+
+ - name: Summarize build results
+ if: ${{ !cancelled() }}
+ shell: bash
+ run: |
+ cat build-results-android-${{ matrix.os }}.log
+ if [[ "${{ job.status }}" != "success" ]]; then
+ exit 1
+ fi
diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml
new file mode 100644
index 00000000..b606cea0
--- /dev/null
+++ b/.github/workflows/desktop.yml
@@ -0,0 +1,14 @@
+name: Desktop builds
+
+on:
+ workflow_dispatch:
+ inputs:
+ apis:
+ description: 'CSV of apis whose quickstart examples we should build'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: noop
+ run: true
diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml
new file mode 100644
index 00000000..72c85f64
--- /dev/null
+++ b/.github/workflows/ios.yml
@@ -0,0 +1,14 @@
+name: iOS builds
+
+on:
+ workflow_dispatch:
+ inputs:
+ apis:
+ description: 'CSV of apis whose quickstart examples we should build'
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: noop
+ run: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..c4f78aab
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+GoogleService-Info.plist
+google-services.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 02f251e9..c02eddc2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -109,7 +109,7 @@ Before you submit your pull request consider the following guidelines:
* Make your changes in a new git branch:
```shell
- git checkout -b my-fix-branch master
+ git checkout -b my-fix-branch main
```
* Create your patch, **including appropriate test cases**.
@@ -133,14 +133,14 @@ Before you submit your pull request consider the following guidelines:
git push origin my-fix-branch
```
-* In GitHub, send a pull request to `firebase/quickstart-cpp:master`.
+* In GitHub, send a pull request to `firebase/quickstart-cpp:main`.
* If we suggest changes then:
* Make the required updates.
* Rebase your branch and force push to your GitHub repository (this will
update your Pull Request):
```shell
- git rebase master -i
+ git rebase main -i
git push origin my-fix-branch -f
```
@@ -158,10 +158,10 @@ the changes from the main (upstream) repository:
git push origin --delete my-fix-branch
```
-* Check out the master branch:
+* Check out the main branch:
```shell
- git checkout master -f
+ git checkout main -f
```
* Delete the local branch:
@@ -170,10 +170,10 @@ the changes from the main (upstream) repository:
git branch -D my-fix-branch
```
-* Update your master with the latest upstream version:
+* Update your main with the latest upstream version:
```shell
- git pull --ff upstream master
+ git pull --ff upstream main
```
## Coding Rules
@@ -186,7 +186,7 @@ Please sign our [Contributor License Agreement][google-cla] (CLA) before sending
pull requests. For any code changes to be accepted, the CLA must be signed.
It's a quick process, we promise!
-*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).*
+*This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/main/CONTRIBUTING.md).*
[github]: https://github.com/firebase/quickstart-cpp
[google-cla]: https://cla.developers.google.com
diff --git a/admob/testapp/Podfile b/admob/testapp/Podfile
deleted file mode 100644
index d7428213..00000000
--- a/admob/testapp/Podfile
+++ /dev/null
@@ -1,7 +0,0 @@
-source 'https://github.com/CocoaPods/Specs.git'
-platform :ios, '7.0'
-# AdMob test application.
-target 'testapp' do
- pod 'Firebase/AdMob'
- pod 'Firebase/Core'
-end
\ No newline at end of file
diff --git a/admob/testapp/build.gradle b/admob/testapp/build.gradle
deleted file mode 100644
index d3060267..00000000
--- a/admob/testapp/build.gradle
+++ /dev/null
@@ -1,117 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- repositories {
- mavenLocal()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:2.1.2'
- classpath 'com.google.gms:google-services:3.0.0'
- }
-}
-
-allprojects {
- repositories {
- mavenLocal()
- jcenter()
- }
-}
-
-apply plugin: 'com.android.application'
-
-// Pre-experimental Gradle plug-in NDK boilerplate below.
-// Right now the Firebase plug-in does not work with the experimental
-// Gradle plug-in so we're using ndk-build for the moment.
-project.ext {
- // Configure the Firebase C++ SDK location.
- firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- if ((new File('firebase_cpp_sdk')).exists()) {
- firebase_cpp_sdk_dir = 'firebase_cpp_sdk'
- } else {
- throw new StopActionException(
- 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' +
- 'environment variable must be set to reference the Firebase C++ ' +
- 'SDK install directory. This is used to configure static library ' +
- 'and C/C++ include paths for the SDK.')
- }
- }
- }
- if (!(new File(firebase_cpp_sdk_dir)).exists()) {
- throw new StopActionException(
- sprintf('Firebase C++ SDK directory %s does not exist',
- firebase_cpp_sdk_dir))
- }
- // Check the NDK location using the same configuration options as the
- // experimental Gradle plug-in.
- ndk_dir = project.android.ndkDirectory
- if (ndk_dir == null || !ndk_dir.exists()) {
- ndk_dir = System.getenv('ANDROID_NDK_HOME')
- if (ndk_dir == null || ndk_dir.isEmpty()) {
- throw new StopActionException(
- 'Android NDK directory should be specified using the ndk.dir ' +
- 'property or ANDROID_NDK_HOME environment variable.')
- }
- }
-}
-
-android {
- compileSdkVersion 23
- buildToolsVersion '23.0.3'
-
- sourceSets {
- main {
- jniLibs.srcDirs = ['libs']
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src/android/java']
- res.srcDirs = ['res']
- }
- }
-
- defaultConfig {
- applicationId 'com.google.android.admob.testapp'
- minSdkVersion 14
- targetSdkVersion 23
- versionCode 1
- versionName '1.0'
- }
- buildTypes {
- release {
- minifyEnabled true
- proguardFile getDefaultProguardFile('proguard-android.txt')
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro")
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/admob.pro")
- }
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.google.android.gms:play-services-ads:9.0.2'
-}
-
-apply plugin: 'com.google.gms.google-services'
-
-task ndkBuildCompile(type:Exec) {
- description 'Use ndk-build to compile the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- sprintf("APP_PLATFORM=android-%d",
- android.defaultConfig.minSdkVersion.mApiLevel))
-}
-
-task ndkBuildClean(type:Exec) {
- description 'Use ndk-build to clean the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- "clean")
-}
-
-// Once the Android Gradle plug-in has generated tasks, add dependencies for
-// the ndk-build targets.
-project.afterEvaluate {
- preBuild.dependsOn(ndkBuildCompile)
- clean.dependsOn(ndkBuildClean)
-}
diff --git a/admob/testapp/jni/Android.mk b/admob/testapp/jni/Android.mk
deleted file mode 100644
index 76ef6dc6..00000000
--- a/admob/testapp/jni/Android.mk
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:=$(call my-dir)/..
-
-ifeq ($(FIREBASE_CPP_SDK_DIR),)
-$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.)
-endif
-
-# With Firebase libraries for the selected build configuration (ABI + STL)
-STL:=$(firstword $(subst _, ,$(APP_STL)))
-FIREBASE_LIBRARY_PATH:=\
-$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_app
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_admob
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libadmob.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=android_main
-LOCAL_SRC_FILES:=\
- $(LOCAL_PATH)/src/common_main.cc \
- $(LOCAL_PATH)/src/android/android_main.cc
-LOCAL_STATIC_LIBRARIES:=\
- firebase_admob \
- firebase_app
-LOCAL_WHOLE_STATIC_LIBRARIES:=\
- android_native_app_glue
-LOCAL_C_INCLUDES:=\
- $(NDK_ROOT)/sources/android/native_app_glue \
- $(LOCAL_PATH)/src
-LOCAL_LDLIBS:=-llog -landroid -latomic
-LOCAL_ARM_MODE:=arm
-LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined
-include $(BUILD_SHARED_LIBRARY)
-
-$(call import-add-path,$(NDK_ROOT)/sources/android)
-$(call import-module,android/native_app_glue)
diff --git a/admob/testapp/jni/Application.mk b/admob/testapp/jni/Application.mk
deleted file mode 100644
index 53ed56a2..00000000
--- a/admob/testapp/jni/Application.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-APP_PLATFORM:=android-14
-NDK_TOOLCHAIN_VERSION=clang
-APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64
-APP_STL:=c++_static
-APP_MODULES:=android_main
-APP_CPPFLAGS+=-std=c++11
diff --git a/admob/testapp/readme.md b/admob/testapp/readme.md
deleted file mode 100644
index 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 8c6ed8d8..00000000
--- a/admob/testapp/src/common_main.cc
+++ /dev/null
@@ -1,280 +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.");
- do {
-#if defined(__ANDROID__)
- app = ::firebase::App::Create(::firebase::AppOptions(), GetJniEnv(),
- GetActivity());
-#else
- app = ::firebase::App::Create(::firebase::AppOptions());
-#endif // defined(__ANDROID__)
-
- if (app == nullptr) {
- LogMessage("Couldn't create firebase app, try again.");
- // Wait a few moments, and try to create app again.
- ProcessEvents(1000);
- }
- } while (app == nullptr);
-
- 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 14f7d906..42939198 100644
--- a/analytics/testapp/build.gradle
+++ b/analytics/testapp/build.gradle
@@ -2,117 +2,75 @@
buildscript {
repositories {
mavenLocal()
+ maven { url 'https://maven.google.com' }
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.1.2'
- classpath 'com.google.gms:google-services:3.0.0'
+ classpath 'com.android.tools.build:gradle:4.2.1'
+ classpath 'com.google.gms:google-services:4.0.1'
}
}
allprojects {
repositories {
mavenLocal()
+ maven { url 'https://maven.google.com' }
jcenter()
}
}
apply plugin: 'com.android.application'
-// Pre-experimental Gradle plug-in NDK boilerplate below.
-// Right now the Firebase plug-in does not work with the experimental
-// Gradle plug-in so we're using ndk-build for the moment.
-project.ext {
- // Configure the Firebase C++ SDK location.
- firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- if ((new File('firebase_cpp_sdk')).exists()) {
- firebase_cpp_sdk_dir = 'firebase_cpp_sdk'
- } else {
- throw new StopActionException(
- 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' +
- 'environment variable must be set to reference the Firebase C++ ' +
- 'SDK install directory. This is used to configure static library ' +
- 'and C/C++ include paths for the SDK.')
- }
- }
- }
- if (!(new File(firebase_cpp_sdk_dir)).exists()) {
- throw new StopActionException(
- sprintf('Firebase C++ SDK directory %s does not exist',
- firebase_cpp_sdk_dir))
- }
- // Check the NDK location using the same configuration options as the
- // experimental Gradle plug-in.
- ndk_dir = project.android.ndkDirectory
- if (ndk_dir == null || !ndk_dir.exists()) {
- ndk_dir = System.getenv('ANDROID_NDK_HOME')
- if (ndk_dir == null || ndk_dir.isEmpty()) {
- throw new StopActionException(
- 'Android NDK directory should be specified using the ndk.dir ' +
- 'property or ANDROID_NDK_HOME environment variable.')
- }
- }
-}
-
android {
- compileSdkVersion 23
- buildToolsVersion '23.0.3'
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
+ }
+ compileSdkVersion 34
+ ndkPath System.getenv('ANDROID_NDK_HOME')
+ buildToolsVersion '30.0.2'
- sourceSets {
- main {
- jniLibs.srcDirs = ['libs']
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src/android/java']
- res.srcDirs = ['res']
- }
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ['libs']
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src/android/java']
+ res.srcDirs = ['res']
}
+ }
- defaultConfig {
- applicationId 'com.google.android.analytics.testapp'
- minSdkVersion 14
- targetSdkVersion 23
- versionCode 1
- versionName '1.0'
+ defaultConfig {
+ applicationId 'com.google.android.analytics.testapp'
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName '1.0'
+ externalNativeBuild.cmake {
+ arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir"
}
- buildTypes {
- release {
- minifyEnabled true
- proguardFile getDefaultProguardFile('proguard-android.txt')
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro")
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/analytics.pro")
- proguardFile file('proguard.pro')
- }
+ }
+ externalNativeBuild.cmake {
+ path 'CMakeLists.txt'
+ }
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ proguardFile file('proguard.pro')
}
+ }
+ packagingOptions {
+ pickFirst 'META-INF/**/coroutines.pro'
+ }
+ lintOptions {
+ abortOnError false
+ checkReleaseBuilds false
+ }
}
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.google.firebase:firebase-analytics:9.0.2'
+apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle"
+firebaseCpp.dependencies {
+ analytics
}
apply plugin: 'com.google.gms.google-services'
-
-task ndkBuildCompile(type:Exec) {
- description 'Use ndk-build to compile the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- sprintf("APP_PLATFORM=android-%d",
- android.defaultConfig.minSdkVersion.mApiLevel))
-}
-
-task ndkBuildClean(type:Exec) {
- description 'Use ndk-build to clean the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- "clean")
-}
-
-// Once the Android Gradle plug-in has generated tasks, add dependencies for
-// the ndk-build targets.
-project.afterEvaluate {
- preBuild.dependsOn(ndkBuildCompile)
- clean.dependsOn(ndkBuildClean)
-}
diff --git a/analytics/testapp/gradle.properties b/analytics/testapp/gradle.properties
new file mode 100644
index 00000000..d7ba8f42
--- /dev/null
+++ b/analytics/testapp/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX = true
diff --git a/analytics/testapp/gradle/wrapper/gradle-wrapper.properties b/analytics/testapp/gradle/wrapper/gradle-wrapper.properties
index d5705170..65340c1b 100644
--- a/analytics/testapp/gradle/wrapper/gradle-wrapper.properties
+++ b/analytics/testapp/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Mon Nov 27 14:03:45 PST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip
diff --git a/analytics/testapp/jni/Android.mk b/analytics/testapp/jni/Android.mk
deleted file mode 100644
index c48cac71..00000000
--- a/analytics/testapp/jni/Android.mk
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:=$(call my-dir)/..
-
-ifeq ($(FIREBASE_CPP_SDK_DIR),)
-$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.)
-endif
-
-# With Firebase libraries for the selected build configuration (ABI + STL)
-STL:=$(firstword $(subst _, ,$(APP_STL)))
-FIREBASE_LIBRARY_PATH:=\
-$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_app
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_analytics
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libanalytics.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=android_main
-LOCAL_SRC_FILES:=\
- $(LOCAL_PATH)/src/common_main.cc \
- $(LOCAL_PATH)/src/android/android_main.cc
-LOCAL_STATIC_LIBRARIES:=\
- firebase_analytics \
- firebase_app
-LOCAL_WHOLE_STATIC_LIBRARIES:=\
- android_native_app_glue
-LOCAL_C_INCLUDES:=\
- $(NDK_ROOT)/sources/android/native_app_glue \
- $(LOCAL_PATH)/src
-LOCAL_LDLIBS:=-llog -landroid -latomic
-LOCAL_ARM_MODE:=arm
-LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined
-include $(BUILD_SHARED_LIBRARY)
-
-$(call import-add-path,$(NDK_ROOT)/sources/android)
-$(call import-module,android/native_app_glue)
diff --git a/analytics/testapp/jni/Application.mk b/analytics/testapp/jni/Application.mk
deleted file mode 100644
index 53ed56a2..00000000
--- a/analytics/testapp/jni/Application.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-APP_PLATFORM:=android-14
-NDK_TOOLCHAIN_VERSION=clang
-APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64
-APP_STL:=c++_static
-APP_MODULES:=android_main
-APP_CPPFLAGS+=-std=c++11
diff --git a/analytics/testapp/proguard.pro b/analytics/testapp/proguard.pro
index c7e9278d..54cd248b 100644
--- a/analytics/testapp/proguard.pro
+++ b/analytics/testapp/proguard.pro
@@ -1 +1,2 @@
+-ignorewarnings
-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; }
diff --git a/analytics/testapp/readme.md b/analytics/testapp/readme.md
index 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 b88479bd..e063e416 100644
--- a/analytics/testapp/src/common_main.cc
+++ b/analytics/testapp/src/common_main.cc
@@ -27,21 +27,12 @@ extern "C" int common_main(int argc, const char* argv[]) {
::firebase::App* app;
LogMessage("Initialize the Analytics library");
- do {
#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__)
- if (app == nullptr) {
- LogMessage("Couldn't create firebase app, try again.");
- // Wait a few moments, and try to create app again.
- ProcessEvents(1000);
- }
- } while (app == nullptr);
-
LogMessage("Created the firebase app %x",
static_cast(reinterpret_cast(app)));
analytics::Initialize(*app);
@@ -49,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.
@@ -60,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 225475d6..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.1.2'
- classpath 'com.google.gms:google-services:3.0.0'
+ classpath 'com.android.tools.build:gradle:4.2.1'
+ classpath 'com.google.gms:google-services:4.0.1'
}
}
allprojects {
repositories {
mavenLocal()
+ maven { url 'https://maven.google.com' }
jcenter()
}
}
apply plugin: 'com.android.application'
-// Pre-experimental Gradle plug-in NDK boilerplate below.
-// Right now the Firebase plug-in does not work with the experimental
-// Gradle plug-in so we're using ndk-build for the moment.
-project.ext {
- // Configure the Firebase C++ SDK location.
- firebase_cpp_sdk_dir = System.getProperty('firebase_cpp_sdk.dir')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- firebase_cpp_sdk_dir = System.getenv('FIREBASE_CPP_SDK_DIR')
- if (firebase_cpp_sdk_dir == null || firebase_cpp_sdk_dir.isEmpty()) {
- if ((new File('firebase_cpp_sdk')).exists()) {
- firebase_cpp_sdk_dir = 'firebase_cpp_sdk'
- } else {
- throw new StopActionException(
- 'firebase_cpp_sdk.dir property or the FIREBASE_CPP_SDK_DIR ' +
- 'environment variable must be set to reference the Firebase C++ ' +
- 'SDK install directory. This is used to configure static library ' +
- 'and C/C++ include paths for the SDK.')
- }
- }
- }
- if (!(new File(firebase_cpp_sdk_dir)).exists()) {
- throw new StopActionException(
- sprintf('Firebase C++ SDK directory %s does not exist',
- firebase_cpp_sdk_dir))
- }
- // Check the NDK location using the same configuration options as the
- // experimental Gradle plug-in.
- ndk_dir = project.android.ndkDirectory
- if (ndk_dir == null || !ndk_dir.exists()) {
- ndk_dir = System.getenv('ANDROID_NDK_HOME')
- if (ndk_dir == null || ndk_dir.isEmpty()) {
- throw new StopActionException(
- 'Android NDK directory should be specified using the ndk.dir ' +
- 'property or ANDROID_NDK_HOME environment variable.')
- }
+android {
+ compileOptions {
+ sourceCompatibility 1.8
+ targetCompatibility 1.8
}
-}
-android {
- compileSdkVersion 23
- buildToolsVersion '23.0.3'
+ compileSdkVersion 34
+ ndkPath System.getenv('ANDROID_NDK_HOME')
+ buildToolsVersion '30.0.2'
- sourceSets {
- main {
- jniLibs.srcDirs = ['libs']
- manifest.srcFile 'AndroidManifest.xml'
- java.srcDirs = ['src/android/java']
- res.srcDirs = ['res']
- }
+ sourceSets {
+ main {
+ jniLibs.srcDirs = ['libs']
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src/android/java']
+ res.srcDirs = ['res']
}
+ }
- defaultConfig {
- applicationId 'com.google.android.auth.testapp'
- minSdkVersion 14
- targetSdkVersion 23
- versionCode 1
- versionName '1.0'
+ defaultConfig {
+ applicationId 'com.google.android.auth.testapp'
+ minSdkVersion 23
+ targetSdkVersion 34
+ versionCode 1
+ versionName '1.0'
+ externalNativeBuild.cmake {
+ arguments "-DFIREBASE_CPP_SDK_DIR=$gradle.firebase_cpp_sdk_dir"
}
- buildTypes {
- release {
- minifyEnabled true
- proguardFile getDefaultProguardFile('proguard-android.txt')
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/app.pro")
- proguardFile file(project.ext.firebase_cpp_sdk_dir + "/libs/android/auth.pro")
- proguardFile file('proguard.pro')
- }
+ }
+ externalNativeBuild.cmake {
+ path 'CMakeLists.txt'
+ }
+ buildTypes {
+ release {
+ minifyEnabled true
+ proguardFile getDefaultProguardFile('proguard-android.txt')
+ proguardFile file('proguard.pro')
}
+ }
+ packagingOptions {
+ pickFirst 'META-INF/**/coroutines.pro'
+ }
+ lintOptions {
+ abortOnError false
+ checkReleaseBuilds false
+ }
}
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.google.firebase:firebase-auth:9.0.2'
-
+apply from: "$gradle.firebase_cpp_sdk_dir/Android/firebase_dependencies.gradle"
+firebaseCpp.dependencies {
+ auth
}
apply plugin: 'com.google.gms.google-services'
-
-task ndkBuildCompile(type:Exec) {
- description 'Use ndk-build to compile the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- sprintf("APP_PLATFORM=android-%d",
- android.defaultConfig.minSdkVersion.mApiLevel))
-}
-
-task ndkBuildClean(type:Exec) {
- description 'Use ndk-build to clean the C++ application.'
- commandLine("${project.ext.ndk_dir}${File.separator}ndk-build",
- "FIREBASE_CPP_SDK_DIR=${project.ext.firebase_cpp_sdk_dir}",
- "clean")
-}
-
-// Once the Android Gradle plug-in has generated tasks, add dependencies for
-// the ndk-build targets.
-project.afterEvaluate {
- preBuild.dependsOn(ndkBuildCompile)
- clean.dependsOn(ndkBuildClean)
-}
diff --git a/auth/testapp/gradle.properties b/auth/testapp/gradle.properties
new file mode 100644
index 00000000..d7ba8f42
--- /dev/null
+++ b/auth/testapp/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX = true
diff --git a/auth/testapp/gradle/wrapper/gradle-wrapper.properties b/auth/testapp/gradle/wrapper/gradle-wrapper.properties
index d5705170..65340c1b 100644
--- a/auth/testapp/gradle/wrapper/gradle-wrapper.properties
+++ b/auth/testapp/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Mon Nov 27 14:03:45 PST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
+distributionUrl=https://services.gradle.org/distributions/gradle-6.7.1-all.zip
diff --git a/auth/testapp/jni/Android.mk b/auth/testapp/jni/Android.mk
deleted file mode 100644
index 3f8e180f..00000000
--- a/auth/testapp/jni/Android.mk
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:=$(call my-dir)/..
-
-ifeq ($(FIREBASE_CPP_SDK_DIR),)
-$(error FIREBASE_CPP_SDK_DIR must specify the Firebase package location.)
-endif
-
-# With Firebase libraries for the selected build configuration (ABI + STL)
-STL:=$(firstword $(subst _, ,$(APP_STL)))
-FIREBASE_LIBRARY_PATH:=\
-$(FIREBASE_CPP_SDK_DIR)/libs/android/$(TARGET_ARCH_ABI)/$(STL)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_app
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libapp.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=firebase_auth
-LOCAL_SRC_FILES:=$(FIREBASE_LIBRARY_PATH)/libauth.a
-LOCAL_EXPORT_C_INCLUDES:=$(FIREBASE_CPP_SDK_DIR)/include
-include $(PREBUILT_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:=android_main
-LOCAL_SRC_FILES:=\
- $(LOCAL_PATH)/src/common_main.cc \
- $(LOCAL_PATH)/src/android/android_main.cc
-LOCAL_STATIC_LIBRARIES:=\
- firebase_auth \
- firebase_app
-LOCAL_WHOLE_STATIC_LIBRARIES:=\
- android_native_app_glue
-LOCAL_C_INCLUDES:=\
- $(NDK_ROOT)/sources/android/native_app_glue \
- $(LOCAL_PATH)/src
-LOCAL_LDLIBS:=-llog -landroid -latomic
-LOCAL_ARM_MODE:=arm
-LOCAL_LDFLAGS:=-Wl,-z,defs -Wl,--no-undefined
-include $(BUILD_SHARED_LIBRARY)
-
-$(call import-add-path,$(NDK_ROOT)/sources/android)
-$(call import-module,android/native_app_glue)
diff --git a/auth/testapp/jni/Application.mk b/auth/testapp/jni/Application.mk
deleted file mode 100644
index 53ed56a2..00000000
--- a/auth/testapp/jni/Application.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright 2016 Google Inc. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-APP_PLATFORM:=android-14
-NDK_TOOLCHAIN_VERSION=clang
-APP_ABI:=armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64
-APP_STL:=c++_static
-APP_MODULES:=android_main
-APP_CPPFLAGS+=-std=c++11
diff --git a/auth/testapp/proguard.pro b/auth/testapp/proguard.pro
index c7e9278d..5edad15e 100644
--- a/auth/testapp/proguard.pro
+++ b/auth/testapp/proguard.pro
@@ -1 +1,3 @@
+-ignorewarnings
-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; }
+-keep,includedescriptorclasses public class com.google.firebase.example.TextEntryField { *; }
diff --git a/auth/testapp/readme.md b/auth/testapp/readme.md
index 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 9cbfc6aa..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,12 +62,20 @@ static const char kCustomEmail[] = "custom.email@example.com";
static const char kCustomPassword[] = "CustomPasswordGoesHere";
// Constants used during tests.
+static const char kTestNonceBad[] = "testBadNonce";
static const char kTestPassword[] = "testEmailPassword123";
static const char kTestEmailBad[] = "bad.test.email@example.com";
static const char kTestPasswordBad[] = "badTestPassword";
static const char kTestIdTokenBad[] = "bad id token for testing";
static const char kTestAccessTokenBad[] = "bad access token for testing";
static const char kTestPasswordUpdated[] = "testpasswordupdated";
+static const char kTestIdProviderIdBad[] = "bad provider id for testing";
+static const char kTestServerAuthCodeBad[] = "bad server auth code";
+
+static const int kWaitIntervalMs = 300;
+static const int kPhoneAuthCodeSendWaitMs = 600000;
+static const int kPhoneAuthCompletionWaitMs = 8000;
+static const int kPhoneAuthTimeoutMs = 0;
static const char kFirebaseProviderId[] =
#if defined(__ANDROID__)
@@ -64,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;
}
@@ -129,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);
}
@@ -139,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);
}
}
@@ -149,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();
@@ -165,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* 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; }
@@ -212,39 +389,133 @@ class UserLogin {
Auth* auth_;
std::string email_;
std::string password_;
- User* user_;
+ User user_;
+ bool log_errors_;
+};
+
+class PhoneListener : public PhoneAuthProvider::Listener {
+ public:
+ PhoneListener()
+ : num_calls_on_verification_complete_(0),
+ num_calls_on_verification_failed_(0),
+ num_calls_on_code_sent_(0),
+ num_calls_on_code_auto_retrieval_time_out_(0) {}
+
+ void OnVerificationCompleted(PhoneAuthCredential /*credential*/) override {
+ LogMessage("PhoneListener: successful automatic verification.");
+ num_calls_on_verification_complete_++;
+ }
+
+ void OnVerificationFailed(const std::string& error) override {
+ LogMessage("ERROR: PhoneListener verification failed with error, %s",
+ error.c_str());
+ num_calls_on_verification_failed_++;
+ }
+
+ void OnCodeSent(const std::string& verification_id,
+ const PhoneAuthProvider::ForceResendingToken&
+ force_resending_token) override {
+ LogMessage("PhoneListener: code sent. verification_id=%s",
+ verification_id.c_str());
+ verification_id_ = verification_id;
+ force_resending_token_ = force_resending_token;
+ num_calls_on_code_sent_++;
+ }
+
+ void OnCodeAutoRetrievalTimeOut(const std::string& verification_id) override {
+ LogMessage("PhoneListener: auto retrieval timeout. verification_id=%s",
+ verification_id.c_str());
+ verification_id_ = verification_id;
+ num_calls_on_code_auto_retrieval_time_out_++;
+ }
+
+ const std::string& verification_id() const { return verification_id_; }
+ const PhoneAuthProvider::ForceResendingToken& force_resending_token() const {
+ return force_resending_token_;
+ }
+ int num_calls_on_verification_complete() const {
+ return num_calls_on_verification_complete_;
+ }
+ int num_calls_on_verification_failed() const {
+ return num_calls_on_verification_failed_;
+ }
+ int num_calls_on_code_sent() const { return num_calls_on_code_sent_; }
+ int num_calls_on_code_auto_retrieval_time_out() const {
+ return num_calls_on_code_auto_retrieval_time_out_;
+ }
+
+ private:
+ std::string verification_id_;
+ PhoneAuthProvider::ForceResendingToken force_resending_token_;
+ int num_calls_on_verification_complete_;
+ int num_calls_on_verification_failed_;
+ int num_calls_on_code_sent_;
+ int num_calls_on_code_auto_retrieval_time_out_;
};
// Execute all methods of the C++ Auth API.
extern "C" int common_main(int argc, const char* argv[]) {
App* app;
LogMessage("Starting Auth tests.");
- do {
-// 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__)
- if (app == nullptr) {
- LogMessage("Couldn't create firebase app, try again.");
- // Wait a few moments, and try to create app again.
- ProcessEvents(1000);
- }
- } while (app == nullptr);
-
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 --------------------------------------------------
@@ -252,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());
}
}
}
@@ -301,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());
@@ -315,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.
@@ -367,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);
}
@@ -426,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.
@@ -434,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);
@@ -581,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);
}
}
}
@@ -603,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/database/testapp/AndroidManifest.xml b/database/testapp/AndroidManifest.xml
new file mode 100644
index 00000000..f94a8ca5
--- /dev/null
+++ b/database/testapp/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/admob/testapp/AndroidManifest.xml b/dynamic_links/testapp/AndroidManifest.xml
similarity index 76%
rename from admob/testapp/AndroidManifest.xml
rename to dynamic_links/testapp/AndroidManifest.xml
index e5da1f5d..2aa60f2d 100644
--- a/admob/testapp/AndroidManifest.xml
+++ b/dynamic_links/testapp/AndroidManifest.xml
@@ -1,15 +1,17 @@
-
+
+ android:exported = "true"
+ android:screenOrientation="portrait"
+ android:configChanges="orientation|screenSize">
diff --git a/dynamic_links/testapp/CMakeLists.txt b/dynamic_links/testapp/CMakeLists.txt
new file mode 100644
index 00000000..5574b351
--- /dev/null
+++ b/dynamic_links/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_dynamic_links firebase_app)
+target_link_libraries(${target_name} "${firebase_libs}" ${ADDITIONAL_LIBS})
diff --git a/invites/testapp/LICENSE b/dynamic_links/testapp/LICENSE
similarity index 99%
rename from invites/testapp/LICENSE
rename to dynamic_links/testapp/LICENSE
index 25dd9993..d6456956 100644
--- a/invites/testapp/LICENSE
+++ b/dynamic_links/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/invites/testapp/LaunchScreen.storyboard b/dynamic_links/testapp/LaunchScreen.storyboard
similarity index 100%
rename from invites/testapp/LaunchScreen.storyboard
rename to dynamic_links/testapp/LaunchScreen.storyboard
diff --git a/dynamic_links/testapp/Podfile b/dynamic_links/testapp/Podfile
new file mode 100644
index 00000000..e983680a
--- /dev/null
+++ b/dynamic_links/testapp/Podfile
@@ -0,0 +1,7 @@
+source 'https://github.com/CocoaPods/Specs.git'
+platform :ios, '13.0'
+use_frameworks!
+# Dynamic Links test application.
+target 'testapp' do
+ pod 'Firebase/DynamicLinks', '10.25.0'
+end
diff --git a/dynamic_links/testapp/build.gradle b/dynamic_links/testapp/build.gradle
new file mode 100644
index 00000000..e7b828bb
--- /dev/null
+++ b/dynamic_links/testapp/build.gradle
@@ -0,0 +1,76 @@
+// 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.dynamiclinks.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 {
+ dynamicLinks
+}
+
+apply plugin: 'com.google.gms.google-services'
diff --git a/dynamic_links/testapp/gradle.properties b/dynamic_links/testapp/gradle.properties
new file mode 100644
index 00000000..d7ba8f42
--- /dev/null
+++ b/dynamic_links/testapp/gradle.properties
@@ -0,0 +1 @@
+android.useAndroidX = true
diff --git a/invites/testapp/gradle/wrapper/gradle-wrapper.jar b/dynamic_links/testapp/gradle/wrapper/gradle-wrapper.jar
similarity index 100%
rename from invites/testapp/gradle/wrapper/gradle-wrapper.jar
rename to dynamic_links/testapp/gradle/wrapper/gradle-wrapper.jar
diff --git a/dynamic_links/testapp/gradle/wrapper/gradle-wrapper.properties b/dynamic_links/testapp/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..475b0f7d
--- /dev/null
+++ b/dynamic_links/testapp/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+#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/invites/testapp/gradlew b/dynamic_links/testapp/gradlew
similarity index 100%
rename from invites/testapp/gradlew
rename to dynamic_links/testapp/gradlew
diff --git a/invites/testapp/gradlew.bat b/dynamic_links/testapp/gradlew.bat
similarity index 100%
rename from invites/testapp/gradlew.bat
rename to dynamic_links/testapp/gradlew.bat
diff --git a/dynamic_links/testapp/proguard.pro b/dynamic_links/testapp/proguard.pro
new file mode 100644
index 00000000..54cd248b
--- /dev/null
+++ b/dynamic_links/testapp/proguard.pro
@@ -0,0 +1,2 @@
+-ignorewarnings
+-keep,includedescriptorclasses public class com.google.firebase.example.LoggingUtils { *; }
diff --git a/dynamic_links/testapp/readme.md b/dynamic_links/testapp/readme.md
new file mode 100644
index 00000000..66190975
--- /dev/null
+++ b/dynamic_links/testapp/readme.md
@@ -0,0 +1,223 @@
+Firebase Dynamic Links Quickstart
+=================================
+
+> [!IMPORTANT]
+> Firebase Dynamic Links is **deprecated** and should not be used in new projects. The service will shut down on August 25, 2025.
+>
+> Please see our [Dynamic Links Deprecation FAQ documentation](https://firebase.google.com/support/dynamic-links-faq) for more guidance.
+
+The Firebase Dynamic Links Quickstart demonstrates logging a range
+of different events using the Firebase Dynamic Links C++ SDK. The application
+has no user interface and simply logs actions it's performing to the console.
+
+Introduction
+------------
+
+- [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 the [Firebase console](https://firebase.google.com/console/), and attach
+ your iOS app to it.
+ - You can use "com.google.FirebaseCppDynamicLinksTestApp.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
+ to the Firebase backend.
+ - Configure the app to handle dynamic links / app invites.
+ - In your project's Info tab, under the URL Types section, find the URL
+ Schemes box and add your app's bundle ID (the default scheme used
+ by dynamic links).
+ - Copy the dynamic links domain for your project under the Dynamic Links
+ tab of the [Firebase console](https://firebase.google.com/console/)
+ Then, in your project's Capabilities tab:
+ - Enable the Associated Domains capability.
+ - Add applinks:YOUR_DYNAMIC_LINKS_DOMAIN
+ For example "applinks:xyz.app.goo.gl".
+ - Copy the dynamic links domain for your project under the Dynamic Links
+ tab of the [Firebase console](https://firebase.google.com/console/)
+ e.g xyz.app.goo.gl and assign to the string kDomainUriPrefix in
+ src/common_main.cc .
+ - Optional: If you want to use a custom Dynamic Links domain, follow
+ [these instructions](https://firebase.google.com/docs/dynamic-links/custom-domains)
+ to set up the domain in Firebase console and in your project's Info.plist.
+ Be sure to assign that domain to the string kDomainUriPrefix in
+ src/common_main.cc.
+ - 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\_dynamic_links.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.
+ - When running the application you should see:
+ - The dynamic link - if any - recevied by the application on startup.
+ - A dynamically generated long link.
+ - A dynamically generated short link.
+ - Leaving the application and opening a link (e.g via an email) for the app
+ should reopen the app and display the dynamic link.
+
+### 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.dynamiclinks.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).
+ - Copy the dynamic links domain for your project under the Dynamic Links
+ tab of the [Firebase console](https://firebase.google.com/console/)
+ e.g xyz.app.goo.gl and assign to the string kDomainUriPrefix in
+ src/common_main.cc .
+ - Optional: If you want to use a custom Dynamic Links domain, follow
+ [these instructions](https://firebase.google.com/docs/dynamic-links/custom-domains)
+ to set up the domain in Firebase console. Be sure to assign that domain to
+ the string kDomainUriPrefix in src/common_main.cc.
+ - 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 user interface. The output of the app can be viewed
+ in the logcat output of Android studio or by running "adb logcat" from
+ the command line.
+ - When running the application you should see:
+ - The dynamic link - if any - recevied by the application on startup.
+ - A dynamically generated long link.
+ - A dynamically generated short link.
+ - Leaving the application and opening a link (e.g via an email) for the app
+ should reopen the app and display the dynamic link.
+
+### 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 Dynamic Links uses a stubbed implementation on desktop,
+ so functionality is not expected.
+
+Support
+-------
+
+[https://firebase.google.com/support/](https://firebase.google.com/support/)
+
+License
+-------
+
+Copyright 2017 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/dynamic_links/testapp/res/layout/main.xml b/dynamic_links/testapp/res/layout/main.xml
new file mode 100644
index 00000000..d3ffb630
--- /dev/null
+++ b/dynamic_links/testapp/res/layout/main.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/dynamic_links/testapp/res/values/strings.xml b/dynamic_links/testapp/res/values/strings.xml
new file mode 100644
index 00000000..33fe18b2
--- /dev/null
+++ b/dynamic_links/testapp/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Firebase Dynamic Links Test
+
diff --git a/dynamic_links/testapp/settings.gradle b/dynamic_links/testapp/settings.gradle
new file mode 100644
index 00000000..2a543b93
--- /dev/null
+++ b/dynamic_links/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/dynamic_links/testapp/src/android/android_main.cc b/dynamic_links/testapp/src/android/android_main.cc
new file mode 100644
index 00000000..4e033e1c
--- /dev/null
+++ b/dynamic_links/testapp/src/android/android_main.cc
@@ -0,0 +1,255 @@
+// 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 = 1000;
+ 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/admob/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/dynamic_links/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 dynamic_links/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/dynamic_links/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/dynamic_links/testapp/src/common_main.cc b/dynamic_links/testapp/src/common_main.cc
new file mode 100644
index 00000000..3201f859
--- /dev/null
+++ b/dynamic_links/testapp/src/common_main.cc
@@ -0,0 +1,203 @@
+// 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/dynamic_links.h"
+#include "firebase/dynamic_links/components.h"
+#include "firebase/future.h"
+#include "firebase/util.h"
+// Thin OS abstraction layer.
+#include "main.h" // NOLINT
+
+// Invalid domain, used to make sure the user sets a valid domain.
+#define INVALID_DOMAIN_URI_PREFIX "THIS_IS_AN_INVALID_DOMAIN"
+
+static const char* kDomainUriPrefixInvalidError =
+ "kDomainUriPrefix is not valid, link shortening will fail.\n"
+ "To resolve this:\n"
+ "* Goto the Firebase console https://firebase.google.com/console/\n"
+ "* Click on the Dynamic Links tab\n"
+ "* Copy the URI prefix e.g https://x20yz.app.goo.gl\n"
+ "* Replace the value of kDomainUriPrefix with the copied URI prefix.\n";
+
+// IMPORTANT: You need to set this to a valid URI prefix from the Firebase
+// console (see kDomainUriPrefixInvalidError for the details).
+static const char* kDomainUriPrefix = INVALID_DOMAIN_URI_PREFIX;
+
+// Displays a received dynamic link.
+class Listener : public firebase::dynamic_links::Listener {
+ public:
+ // Called on the client when a dynamic link arrives.
+ void OnDynamicLinkReceived(
+ const firebase::dynamic_links::DynamicLink* dynamic_link) override {
+ LogMessage("Received link: %s", dynamic_link->url.c_str());
+ }
+};
+
+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());
+ }
+}
+
+// Show a generated link.
+void ShowGeneratedLink(
+ const firebase::dynamic_links::GeneratedDynamicLink& generated_link,
+ const char* operation_description) {
+ if (!generated_link.warnings.empty()) {
+ LogMessage("%s generated warnings:", operation_description);
+ for (auto it = generated_link.warnings.begin();
+ it != generated_link.warnings.end(); ++it) {
+ LogMessage(" %s", it->c_str());
+ }
+ }
+ LogMessage("url: %s", generated_link.url.c_str());
+}
+
+// Wait for dynamic link generation to complete, logging the result.
+void WaitForAndShowGeneratedLink(
+ const firebase::Future&
+ generated_dynamic_link_future,
+ const char* operation_description) {
+ LogMessage("%s...", operation_description);
+ WaitForCompletion(generated_dynamic_link_future, operation_description);
+ if (generated_dynamic_link_future.error() != 0) {
+ LogMessage("ERROR: %s failed with error %d: %s", operation_description,
+ generated_dynamic_link_future.error(),
+ generated_dynamic_link_future.error_message());
+ return;
+ }
+ ShowGeneratedLink(*generated_dynamic_link_future.result(),
+ operation_description);
+}
+
+// Execute all methods of the C++ Dynamic Links API.
+extern "C" int common_main(int argc, const char* argv[]) {
+ namespace dynamic_links = ::firebase::dynamic_links;
+ ::firebase::App* app;
+ Listener* link_listener = new Listener;
+
+ LogMessage("Initialize the Firebase Dynamic Links library");
+#if defined(__ANDROID__)
+ app = ::firebase::App::Create(GetJniEnv(), GetActivity());
+#else
+ app = ::firebase::App::Create();
+#endif // defined(__ANDROID__)
+
+ LogMessage("Created the Firebase app %x",
+ static_cast(reinterpret_cast(app)));
+
+ ::firebase::ModuleInitializer initializer;
+ initializer.Initialize(app, link_listener,
+ [](::firebase::App* app, void* listener) {
+ LogMessage("Try to initialize Dynamic Links");
+ return ::firebase::dynamic_links::Initialize(
+ *app, reinterpret_cast(listener));
+ });
+ while (initializer.InitializeLastResult().status() !=
+ firebase::kFutureStatusComplete) {
+ if (ProcessEvents(100)) return 1; // exit if requested
+ }
+ if (initializer.InitializeLastResult().error() != 0) {
+ LogMessage("Failed to initialize Firebase Dynamic Links: %s",
+ initializer.InitializeLastResult().error_message());
+ ProcessEvents(2000);
+ return 1;
+ }
+
+ LogMessage("Initialized the Firebase Dynamic Links API");
+
+ firebase::dynamic_links::GoogleAnalyticsParameters analytics_parameters;
+ analytics_parameters.source = "mysource";
+ analytics_parameters.medium = "mymedium";
+ analytics_parameters.campaign = "mycampaign";
+ analytics_parameters.term = "myterm";
+ analytics_parameters.content = "mycontent";
+
+ firebase::dynamic_links::IOSParameters ios_parameters("com.myapp.bundleid");
+ ios_parameters.fallback_url = "https://mysite/fallback";
+ ios_parameters.custom_scheme = "mycustomscheme";
+ ios_parameters.minimum_version = "1.2.3";
+ ios_parameters.ipad_bundle_id = "com.myapp.bundleid.ipad";
+ ios_parameters.ipad_fallback_url = "https://mysite/fallbackipad";
+
+ firebase::dynamic_links::ITunesConnectAnalyticsParameters
+ app_store_parameters;
+ app_store_parameters.affiliate_token = "abcdefg";
+ app_store_parameters.campaign_token = "hijklmno";
+ app_store_parameters.provider_token = "pq-rstuv";
+
+ firebase::dynamic_links::AndroidParameters android_parameters(
+ "com.myapp.packageid");
+ android_parameters.fallback_url = "https://mysite/fallback";
+ android_parameters.minimum_version = 12;
+
+ firebase::dynamic_links::SocialMetaTagParameters social_parameters;
+ social_parameters.title = "My App!";
+ social_parameters.description = "My app is awesome!";
+ social_parameters.image_url = "https://mysite.com/someimage.jpg";
+
+ firebase::dynamic_links::DynamicLinkComponents components(
+ "https://google.com/abc", kDomainUriPrefix);
+ components.google_analytics_parameters = &analytics_parameters;
+ components.ios_parameters = &ios_parameters;
+ components.itunes_connect_analytics_parameters = &app_store_parameters;
+ components.android_parameters = &android_parameters;
+ components.social_meta_tag_parameters = &social_parameters;
+
+ dynamic_links::GeneratedDynamicLink long_link;
+ {
+ const char* description = "Generate long link from components";
+ long_link = dynamic_links::GetLongLink(components);
+ LogMessage("%s...", description);
+ ShowGeneratedLink(long_link, description);
+ }
+
+ if (strcmp(kDomainUriPrefix, INVALID_DOMAIN_URI_PREFIX) == 0) {
+ LogMessage(kDomainUriPrefixInvalidError);
+ } else {
+ {
+ firebase::Future link_future =
+ dynamic_links::GetShortLink(components);
+ WaitForAndShowGeneratedLink(link_future,
+ "Generate short link from components");
+ }
+ if (!long_link.url.empty()) {
+ dynamic_links::DynamicLinkOptions options;
+ options.path_length = firebase::dynamic_links::kPathLengthShort;
+ firebase::Future link_future =
+ dynamic_links::GetShortLink(long_link.url.c_str(), options);
+ WaitForAndShowGeneratedLink(link_future, "Generate short from long link");
+ }
+ }
+
+ // Wait until the user wants to quit the app.
+ while (!ProcessEvents(1000)) {
+ }
+
+ dynamic_links::Terminate();
+ delete link_listener;
+ delete app;
+
+ return 0;
+}
diff --git a/admob/testapp/src/desktop/desktop_main.cc b/dynamic_links/testapp/src/desktop/desktop_main.cc
similarity index 52%
rename from admob/testapp/src/desktop/desktop_main.cc
rename to dynamic_links/testapp/src/desktop/desktop_main.cc
index 00e57132..0220c688 100644
--- a/admob/testapp/src/desktop/desktop_main.cc
+++ b/dynamic_links/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/invites/testapp/src/ios/ios_main.mm b/dynamic_links/testapp/src/ios/ios_main.mm
similarity index 98%
rename from invites/testapp/src/ios/ios_main.mm
rename to dynamic_links/testapp/src/ios/ios_main.mm
index 6ccb2de5..2adcac9c 100644
--- a/invites/testapp/src/ios/ios_main.mm
+++ b/dynamic_links/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/invites/testapp/src/main.h b/dynamic_links/testapp/src/main.h
similarity index 100%
rename from invites/testapp/src/main.h
rename to dynamic_links/testapp/src/main.h
diff --git a/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj b/dynamic_links/testapp/testapp.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..5769362c
--- /dev/null
+++ b/dynamic_links/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/dynamic_links/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json b/dynamic_links/testapp/testapp/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..b7f3352e
--- /dev/null
+++ b/dynamic_links/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/dynamic_links/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json b/dynamic_links/testapp/testapp/Images.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 00000000..6f870a46
--- /dev/null
+++ b/dynamic_links/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/invites/testapp/testapp/Info.plist b/dynamic_links/testapp/testapp/Info.plist
similarity index 78%
rename from invites/testapp/testapp/Info.plist
rename to dynamic_links/testapp/testapp/Info.plist
index c86c4546..c9ef93fc 100644
--- a/invites/testapp/testapp/Info.plist
+++ b/dynamic_links/testapp/testapp/Info.plist
@@ -7,7 +7,7 @@
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
- com.google.ios.invites.testapp
+ com.google.FirebaseCppDynamicLinksTestApp.dev
CFBundleInfoDictionaryVersion
6.0
CFBundleName
@@ -16,11 +16,9 @@
APPL
CFBundleShortVersionString
1.0
- CFBundleSignature
- ????
CFBundleURLSchemes
- com.google.ios.invites.testapp
+ com.google.FirebaseCppDynamicLinksTestApp.dev
CFBundleURLTypes
@@ -28,10 +26,10 @@
CFBundleTypeRole
Editor
CFBundleURLName
- com.google.ios.invites.testapp
+ com.google.FirebaseCppDynamicLinksTestApp.dev
CFBundleURLSchemes
- com.google.ios.invites.testapp
+ com.google.FirebaseCppDynamicLinksTestApp.dev
@@ -51,5 +49,7 @@
UILaunchStoryboardName
LaunchScreen
+ NSContactsUsageDescription
+ Invite others to use the app.
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/firestore/testapp/LaunchScreen.storyboard b/firestore/testapp/LaunchScreen.storyboard
new file mode 100644
index 00000000..673e0f7e
--- /dev/null
+++ b/firestore/testapp/LaunchScreen.storyboard
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
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/firestore/testapp/gradle/wrapper/gradle-wrapper.jar b/firestore/testapp/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
Binary files /dev/null and b/firestore/testapp/gradle/wrapper/gradle-wrapper.jar differ
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/firestore/testapp/gradlew b/firestore/testapp/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/firestore/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/firestore/testapp/gradlew.bat b/firestore/testapp/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/firestore/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/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/firestore/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java b/firestore/testapp/src/android/java/com/google/firebase/example/LoggingUtils.java
new file mode 100644
index 00000000..11d67c5b
--- /dev/null
+++ b/firestore/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/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