SkAR: drawing text, shapes, rotation modes enabled, translating objects

To run this app, you need to create an out directory as such:
bin/gn gen out/arm64 --args='ndk="NDK_PATH" target_cpu="ABI"'

For now, the only supported ABI is arm64

Change-Id: I012f0c6a0550d80a0028f42177d5ca72974d848d
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/130980
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Mike Klein <mtklein@google.com>
Commit-Queue: Ziad Ben Hadj-Alouane <ziadb@google.com>
diff --git a/.gitignore b/.gitignore
index 77f711d..0de0124 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,10 @@
 platform_tools/android/apps/*.properties
 platform_tools/android/apps/*/build
 platform_tools/android/apps/*/src/main/libs
+platform_tools/android/apps/*.hprof
+platform_tools/android/apps/*/.externalNativeBuild
+
+sampleapp_prefs.txt
 /skps
 third_party/externals
 tools/skp/page_sets/data/*.json
diff --git a/BUILD.gn b/BUILD.gn
index 26cd616..0dd4fb1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1995,6 +1995,60 @@
     ]
   }
 
+  if (is_android) {
+    test_app("arcore") {
+      is_shared_library = true
+      configs = [
+        ":skia_public",
+        "gn:default",
+      ]
+
+      # For internship expedience, yes, we're rebuilding Skia rather than depending on :skia.
+      # At the moment there's no way to use Skia and Skottie/SkShaper unless they're in the same .so.
+      sources = []
+      sources += skia_core_sources
+      sources += skia_utils_sources
+      sources += skia_xps_sources
+      sources += [
+        "src/android/SkAndroidFrameworkUtils.cpp",
+        "src/android/SkAnimatedImage.cpp",
+        "src/android/SkBitmapRegionCodec.cpp",
+        "src/android/SkBitmapRegionDecoder.cpp",
+        "src/codec/SkAndroidCodec.cpp",
+        "src/codec/SkBmpBaseCodec.cpp",
+        "src/codec/SkBmpCodec.cpp",
+        "src/codec/SkBmpMaskCodec.cpp",
+        "src/codec/SkBmpRLECodec.cpp",
+        "src/codec/SkBmpStandardCodec.cpp",
+        "src/codec/SkCodec.cpp",
+        "src/codec/SkCodecImageGenerator.cpp",
+        "src/codec/SkColorTable.cpp",
+        "src/codec/SkGifCodec.cpp",
+        "src/codec/SkMaskSwizzler.cpp",
+        "src/codec/SkMasks.cpp",
+        "src/codec/SkSampledCodec.cpp",
+        "src/codec/SkSampler.cpp",
+        "src/codec/SkStreamBuffer.cpp",
+        "src/codec/SkSwizzler.cpp",
+        "src/codec/SkWbmpCodec.cpp",
+        "src/images/SkImageEncoder.cpp",
+        "src/ports/SkDiscardableMemory_none.cpp",
+        "src/ports/SkImageGenerator_skia.cpp",
+        "src/ports/SkMemory_malloc.cpp",
+        "src/ports/SkOSFile_stdio.cpp",
+        "src/sfnt/SkOTTable_name.cpp",
+        "src/sfnt/SkOTUtils.cpp",
+        "src/utils/mac/SkStream_mac.cpp",
+        "third_party/gif/SkGifImageReader.cpp",
+      ]
+      deps = [
+        ":tool_utils",
+        "modules/skottie",
+        "modules/skshaper",
+      ]
+    }
+  }
+
   if (!skia_use_angle && (is_linux || is_win || is_mac)) {
     test_app("HelloWorld") {
       sources = [
diff --git a/include/utils/Sk3D.h b/include/utils/Sk3D.h
new file mode 100644
index 0000000..f182811
--- /dev/null
+++ b/include/utils/Sk3D.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef Sk3D_DEFINED
+#define Sk3D_DEFINED
+
+#include "SkPoint3.h"
+#include "SkMatrix44.h"
+
+SK_API void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up);
+SK_API bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle);
+SK_API void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count);
+
+#endif
+
diff --git a/platform_tools/android/apps/arcore/CMakeLists.txt b/platform_tools/android/apps/arcore/CMakeLists.txt
new file mode 100644
index 0000000..92c5968
--- /dev/null
+++ b/platform_tools/android/apps/arcore/CMakeLists.txt
@@ -0,0 +1,74 @@
+# Copyright (C) 2018 Google Inc.
+#
+# 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.
+##
+
+# Sets the minimum version of CMake required to build the native library.
+cmake_minimum_required(VERSION 3.4.1)
+
+# Import the ARCore library.
+add_library(arcore SHARED IMPORTED)
+set_target_properties(arcore PROPERTIES IMPORTED_LOCATION
+                      "${ARCORE_LIBPATH}/${ANDROID_ABI}/libarcore_sdk_c.so")
+
+add_library(sk_skia SHARED IMPORTED)
+set_target_properties(sk_skia PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/src/main/libs/${ANDROID_ABI}/libarcore.so")
+
+# This is the main app library.
+add_library(hello_ar_native SHARED
+           "src/main/cpp/hello_ar_application.cc"
+           "src/main/cpp/background_renderer.cc"
+           "src/main/cpp/jni_interface.cc"
+           "src/main/cpp/plane_renderer.cc"
+           "src/main/cpp/point_cloud_renderer.cc"
+           "src/main/cpp/util.cc"
+           "src/main/cpp/pending_anchor.cc"
+           "src/main/cpp/anchor_wrapper.cc")
+
+target_include_directories(hello_ar_native PRIVATE
+            #BASIC AR NATIVE CODE
+            "src/main/cpp"
+
+            #ARCORE LIBRARY
+            "${ARCORE_INCLUDE}"
+
+            #GLM
+            "${ANDROID_NDK}/sources/third_party/vulkan/src/libs/glm"
+
+            #SKIA INCLUDE DIRECTORIES
+            "${SKIA_INCLUDE_PATH}/../modules/skshaper/include"
+            "${SKIA_INCLUDE_PATH}/../modules/skottie/include"
+            "${SKIA_INCLUDE_PATH}/../tools"
+            "${SKIA_INCLUDE_PATH}/../gm"
+            "${SKIA_INCLUDE_PATH}/core"
+            "${SKIA_INCLUDE_PATH}/config"
+            "${SKIA_INCLUDE_PATH}/gpu"
+            "${SKIA_INCLUDE_PATH}/android"
+            "${SKIA_INCLUDE_PATH}/atlastext"
+            "${SKIA_INCLUDE_PATH}/c"
+            "${SKIA_INCLUDE_PATH}/codec"
+            "${SKIA_INCLUDE_PATH}/effects"
+            "${SKIA_INCLUDE_PATH}/encode"
+            "${SKIA_INCLUDE_PATH}/pathops"
+            "${SKIA_INCLUDE_PATH}/ports"
+            "${SKIA_INCLUDE_PATH}/private"
+            "${SKIA_INCLUDE_PATH}/svg"
+            "${SKIA_INCLUDE_PATH}/utils"
+            "${SKIA_INCLUDE_PATH}/views")
+
+target_link_libraries(hello_ar_native
+                      android
+                      log
+                      GLESv2
+                      arcore
+                      sk_skia)
diff --git a/platform_tools/android/apps/arcore/build.gradle b/platform_tools/android/apps/arcore/build.gradle
new file mode 100644
index 0000000..0e82ab8
--- /dev/null
+++ b/platform_tools/android/apps/arcore/build.gradle
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+apply plugin: 'com.android.application'
+
+/*
+The arcore aar library contains the native shared libraries.  These are
+extracted before building to a temporary directory.
+ */
+def arcore_libpath = "${buildDir}/arcore-native"
+
+// Create a configuration to mark which aars to extract .so files from
+configurations { natives }
+
+android {
+    sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
+    sourceSets.main.jniLibs.srcDir "src/main/libs"
+    productFlavors { arm64 {} }
+
+    setupSkiaLibraryBuild(project, applicationVariants, "libarcore")
+
+    compileSdkVersion 27
+    defaultConfig {
+        applicationId "org.skia.viewer"
+        // 24 is the minimum since ARCore only works with 24 and higher.
+        minSdkVersion 24
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+
+        externalNativeBuild {
+            cmake {
+                cppFlags "-std=c++11", "-Wall"
+                arguments "-DANDROID_STL=c++_static",
+                        "-DARCORE_LIBPATH=${arcore_libpath}/jni",
+                        "-DARCORE_INCLUDE=${project.rootDir}/../../libraries/include",
+                        "-DSKIA_INCLUDE_PATH=${project.rootDir}/../../../include"
+            }
+        }
+        ndk {
+            abiFilters "arm64-v8a"
+        }
+    }
+    flavorDimensions "base"
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+
+}
+
+dependencies {
+    // ARCore library
+    implementation 'com.google.ar:core:1.2.0'
+    natives 'com.google.ar:core:1.2.0'
+
+    implementation 'com.android.support:appcompat-v7:27.0.2'
+    implementation 'com.android.support:design:27.0.2'
+}
+
+// Extracts the shared libraries from aars in the natives configuration.
+// This is done so that NDK builds can access these libraries.
+task extractNativeLibraries() {
+    doFirst {
+        configurations.natives.files.each { f ->
+            copy {
+                from zipTree(f)
+                into arcore_libpath
+                include "jni/**/*"
+            }
+        }
+    }
+}
+
+
+tasks.whenTaskAdded {
+    task-> if (task.name.contains("external") && !task.name.contains("Clean")) {
+        task.dependsOn(extractNativeLibraries)
+
+        //make sure skia lib is built and copied in the correct directory before building arcore
+        tasks.whenTaskAdded {
+            t-> if (t.name.contains("CopySkiaLib")) {
+                task.dependsOn(t)
+            }
+        }
+    }
+}
+
diff --git a/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml b/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..975dc25
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.skia.arcore">
+
+  <uses-permission android:name="android.permission.CAMERA"/>
+  <!-- This tag indicates that this application requires ARCore.  This results in the application
+       only being visible in the Google Play Store on devices that support ARCore. -->
+  <uses-feature android:name="android.hardware.camera.ar" android:required="true"/>
+  <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+  <application
+      android:allowBackup="true"
+      android:icon="@drawable/ic_launcher"
+      android:label="@string/app_name"
+      android:theme="@style/Theme.AppCompat.Light.NoActionBar"
+      android:usesCleartextTraffic="false">
+    <!-- This tag indicates that this application requires ARCore.  This results in the Google Play
+          Store downloading and installing ARCore along with the application. -->
+    <meta-data android:name="com.google.ar.core" android:value="required" />
+
+    <activity
+        android:name=".HelloArActivity"
+        android:label="@string/app_name"
+        android:configChanges="orientation|screenSize"
+        android:exported="true"
+        android:theme="@style/Theme.AppCompat.DayNight.NoActionBar"
+
+        android:screenOrientation="locked">
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.LAUNCHER"/>
+      </intent-filter>
+    </activity>
+  </application>
+</manifest>
diff --git a/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png b/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png
new file mode 100644
index 0000000..d85eedf
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/assets/models/trigrid.png
Binary files differ
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc
new file mode 100644
index 0000000..832a8b0
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hello_ar_application.h"
+#include "arcore_c_api.h"
+#include "anchor_wrapper.h"
+
+namespace hello_ar {
+
+    AnchorWrapper::AnchorWrapper(ArAnchor *anchor) : anchor(anchor) {}
+
+    const ArAnchor* AnchorWrapper::GetArAnchor() {
+        return anchor;
+    }
+    DrawableType AnchorWrapper::GetDrawableType() {
+        return drawableType;
+    }
+
+    void AnchorWrapper::SetArAnchor(ArAnchor* anchor) {
+        this->anchor = anchor;
+    }
+    void AnchorWrapper::SetDrawableType(DrawableType drawableType) {
+        this->drawableType = drawableType;
+    }
+
+
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h
new file mode 100644
index 0000000..ff05e57
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/anchor_wrapper.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLO_AR_ANCHOR_WRAPPER_H_
+#define C_ARCORE_HELLO_AR_ANCHOR_WRAPPER_H_
+#include "arcore_c_api.h"
+
+namespace hello_ar {
+    enum DrawableType {
+        TEXT = 0, CIRCLE = 1, RECT = 2
+    };
+
+    class AnchorWrapper {
+    public:
+        AnchorWrapper(ArAnchor* anchor);
+
+        const ArAnchor* GetArAnchor();
+        DrawableType GetDrawableType();
+        bool GetInEditMode();
+
+        void SetArAnchor(ArAnchor* anchor);
+        void SetDrawableType(DrawableType drawableType);
+        void SetInEditMode(bool inEditMode);
+
+    private:
+        ArAnchor* anchor;
+        DrawableType drawableType;
+        bool inEditMode = false;
+    };
+}  // namespace hello_ar
+
+#endif
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc
new file mode 100644
index 0000000..ea7a7c7
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.cc
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 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.
+ */
+
+// This modules handles drawing the passthrough camera image into the OpenGL
+// scene.
+
+#include <type_traits>
+
+#include "background_renderer.h"
+
+namespace hello_ar {
+    namespace {
+// Positions of the quad vertices in clip space (X, Y, Z).
+        const GLfloat kVertices[] = {
+                -1.0f, -1.0f, 0.0f, +1.0f, -1.0f, 0.0f,
+                -1.0f, +1.0f, 0.0f, +1.0f, +1.0f, 0.0f,
+        };
+
+// UVs of the quad vertices (S, T)
+        const GLfloat kUvs[] = {
+                0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
+        };
+
+        constexpr char kVertexShader[] = R"(
+    attribute vec4 vertex;
+    attribute vec2 textureCoords;
+    varying vec2 v_textureCoords;
+    void main() {
+      v_textureCoords = textureCoords;
+      gl_Position = vertex;
+    })";
+
+        constexpr char kFragmentShader[] = R"(
+    #extension GL_OES_EGL_image_external : require
+    precision mediump float;
+    uniform samplerExternalOES texture;
+    varying vec2 v_textureCoords;
+    void main() {
+      gl_FragColor = texture2D(texture, v_textureCoords);
+    })";
+
+    }  // namespace
+
+    void BackgroundRenderer::InitializeGlContent() {
+        shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader);
+
+        if (!shader_program_) {
+            LOGE("Could not create program.");
+        }
+
+        glGenTextures(1, &texture_id_);
+        glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_);
+        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        uniform_texture_ = glGetUniformLocation(shader_program_, "texture");
+        attribute_vertices_ = glGetAttribLocation(shader_program_, "vertex");
+        attribute_uvs_ = glGetAttribLocation(shader_program_, "textureCoords");
+    }
+
+    void BackgroundRenderer::Draw(const ArSession *session, const ArFrame *frame) {
+        static_assert(std::extent<decltype(kUvs)>::value == kNumVertices * 2,
+                      "Incorrect kUvs length");
+        static_assert(std::extent<decltype(kVertices)>::value == kNumVertices * 3,
+                      "Incorrect kVertices length");
+
+        // If display rotation changed (also includes view size change), we need to
+        // re-query the uv coordinates for the on-screen portion of the camera image.
+        int32_t geometry_changed = 0;
+        ArFrame_getDisplayGeometryChanged(session, frame, &geometry_changed);
+        if (geometry_changed != 0 || !uvs_initialized_) {
+            ArFrame_transformDisplayUvCoords(session, frame, kNumVertices * 2, kUvs,
+                                             transformed_uvs_);
+            uvs_initialized_ = true;
+        }
+        glUseProgram(shader_program_);
+        glDepthMask(GL_FALSE);
+
+        glUniform1i(uniform_texture_, 1);
+        glActiveTexture(GL_TEXTURE1);
+        glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_id_);
+
+        glEnableVertexAttribArray(attribute_vertices_);
+        glVertexAttribPointer(attribute_vertices_, 3, GL_FLOAT, GL_FALSE, 0,
+                              kVertices);
+
+        glEnableVertexAttribArray(attribute_uvs_);
+        glVertexAttribPointer(attribute_uvs_, 2, GL_FLOAT, GL_FALSE, 0,
+                              transformed_uvs_);
+
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
+        glUseProgram(0);
+        glDepthMask(GL_TRUE);
+        util::CheckGlError("BackgroundRenderer::Draw() error");
+    }
+
+    GLuint BackgroundRenderer::GetTextureId() const { return texture_id_; }
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h
new file mode 100644
index 0000000..0e4f701
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/background_renderer.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_
+#define C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <cstdlib>
+
+#include "arcore_c_api.h"
+#include "util.h"
+
+namespace hello_ar {
+
+// This class renders the passthrough camera image into the OpenGL frame.
+    class BackgroundRenderer {
+    public:
+        BackgroundRenderer() = default;
+
+        ~BackgroundRenderer() = default;
+
+        // Sets up OpenGL state.  Must be called on the OpenGL thread and before any
+        // other methods below.
+        void InitializeGlContent();
+
+        // Draws the background image.  This methods must be called for every ArFrame
+        // returned by ArSession_update() to catch display geometry change events.
+        void Draw(const ArSession *session, const ArFrame *frame);
+
+        // Returns the generated texture name for the GL_TEXTURE_EXTERNAL_OES target.
+        GLuint GetTextureId() const;
+
+    private:
+        static constexpr int kNumVertices = 4;
+
+        GLuint shader_program_;
+        GLuint texture_id_;
+
+        GLuint attribute_vertices_;
+        GLuint attribute_uvs_;
+        GLuint uniform_texture_;
+
+        float transformed_uvs_[kNumVertices * 2];
+        bool uvs_initialized_ = false;
+    };
+}  // namespace hello_ar
+#endif  // C_ARCORE_HELLO_AR_BACKGROUND_RENDERER_H_
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/glm.h b/platform_tools/android/apps/arcore/src/main/cpp/glm.h
new file mode 100644
index 0000000..cf2a844
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/glm.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef C_ARCORE_HELLOE_AR_GLM_H_
+#define C_ARCORE_HELLOE_AR_GLM_H_
+
+#define GLM_FORCE_RADIANS 1
+#include "glm.hpp"
+#include "gtc/matrix_transform.hpp"
+#include "gtc/type_ptr.hpp"
+#include "gtx/quaternion.hpp"
+
+#endif
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
new file mode 100644
index 0000000..6e050f3
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.cc
@@ -0,0 +1,987 @@
+/*
+ * Copyright 2017 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 "hello_ar_application.h"
+#include <gtx/string_cast.hpp>
+
+#include "anchor_wrapper.h"
+#include "plane_renderer.h"
+#include "pending_anchor.h"
+#include "util.h"
+#include "SkCanvas.h"
+#include "GrContext.h"
+#include "gl/GrGLTypes.h"
+#include "SkSurface.h"
+#include "SkTypeface.h"
+#include "SkFontStyle.h"
+#include "GrBackendSurface.h"
+#include "SkMatrix44.h"
+#include "SkMatrix.h"
+#include "SkTextBlob.h"
+#include "glm.h"
+#include "SkPoint3.h"
+#include "Sk3D.h"
+#include <math.h>       /* acos */
+#include "SkShaper.h"
+#include "Skottie.h"
+#include "SkAnimTimer.h"
+#include "Resources.h"
+#include "SkStream.h"
+
+namespace hello_ar {
+    namespace {
+        constexpr size_t kMaxNumberOfAndroidsToRender = 1;
+        constexpr int32_t kPlaneColorRgbaSize = 16;
+
+        const glm::vec3 kWhite = {255, 255, 255};
+
+        constexpr std::array<uint32_t, kPlaneColorRgbaSize> kPlaneColorRgba = {
+                {0xFFFFFFFF, 0xF44336FF, 0xE91E63FF, 0x9C27B0FF, 0x673AB7FF, 0x3F51B5FF,
+                        0x2196F3FF, 0x03A9F4FF, 0x00BCD4FF, 0x009688FF, 0x4CAF50FF, 0x8BC34AFF,
+                        0xCDDC39FF, 0xFFEB3BFF, 0xFFC107FF, 0xFF9800FF}};
+
+        inline glm::vec3 GetRandomPlaneColor() {
+            const int32_t colorRgba = kPlaneColorRgba[std::rand() % kPlaneColorRgbaSize];
+            return glm::vec3(((colorRgba >> 24) & 0xff) / 255.0f,
+                             ((colorRgba >> 16) & 0xff) / 255.0f,
+                             ((colorRgba >> 8) & 0xff) / 255.0f);
+        }
+    }  // namespace
+
+    HelloArApplication::HelloArApplication(AAssetManager *asset_manager)
+            : asset_manager_(asset_manager) {
+        LOGI("OnCreate()");
+    }
+
+    HelloArApplication::~HelloArApplication() {
+        if (ar_session_ != nullptr) {
+            ArSession_destroy(ar_session_);
+            ArFrame_destroy(ar_frame_);
+        }
+    }
+
+    void HelloArApplication::OnPause() {
+        LOGI("OnPause()");
+        if (ar_session_ != nullptr) {
+            ArSession_pause(ar_session_);
+        }
+    }
+
+    void HelloArApplication::OnResume(void *env, void *context, void *activity) {
+        LOGI("OnResume()");
+
+        if (ar_session_ == nullptr) {
+            ArInstallStatus install_status;
+            // If install was not yet requested, that means that we are resuming the
+            // activity first time because of explicit user interaction (such as
+            // launching the application)
+            bool user_requested_install = !install_requested_;
+
+            // === ATTENTION!  ATTENTION!  ATTENTION! ===
+            // This method can and will fail in user-facing situations.  Your
+            // application must handle these cases at least somewhat gracefully.  See
+            // HelloAR Java sample code for reasonable behavior.
+            CHECK(ArCoreApk_requestInstall(env, activity, user_requested_install,
+                                           &install_status) == AR_SUCCESS);
+
+            switch (install_status) {
+                case AR_INSTALL_STATUS_INSTALLED:
+                    break;
+                case AR_INSTALL_STATUS_INSTALL_REQUESTED:
+                    install_requested_ = true;
+                    return;
+            }
+
+            // === ATTENTION!  ATTENTION!  ATTENTION! ===
+            // This method can and will fail in user-facing situations.  Your
+            // application must handle these cases at least somewhat gracefully.  See
+            // HelloAR Java sample code for reasonable behavior.
+            CHECK(ArSession_create(env, context, &ar_session_) == AR_SUCCESS);
+            CHECK(ar_session_);
+
+            ArFrame_create(ar_session_, &ar_frame_);
+            CHECK(ar_frame_);
+
+            ArSession_setDisplayGeometry(ar_session_, display_rotation_, width_,
+                                         height_);
+        }
+
+        const ArStatus status = ArSession_resume(ar_session_);
+        CHECK(status == AR_SUCCESS);
+    }
+
+    void HelloArApplication::OnSurfaceCreated() {
+        LOGI("OnSurfaceCreated()");
+
+        background_renderer_.InitializeGlContent();
+        point_cloud_renderer_.InitializeGlContent();
+        plane_renderer_.InitializeGlContent(asset_manager_);
+    }
+
+    void HelloArApplication::OnDisplayGeometryChanged(int display_rotation,
+                                                      int width, int height) {
+        LOGI("OnSurfaceChanged(%d, %d)", width, height);
+        glViewport(0, 0, width, height);
+        display_rotation_ = display_rotation;
+        width_ = width;
+        height_ = height;
+
+        if (ar_session_ != nullptr) {
+            ArSession_setDisplayGeometry(ar_session_, display_rotation, width, height);;
+        }
+    }
+
+    void HelloArApplication::OnObjectRotationChanged(int rotation) {
+        LOGI("OnObjectRotationChanged(%d)", rotation);
+        currentObjectRotation = rotation;
+    }
+
+    void HelloArApplication::OnAction(float value) {
+        LOGI("OnAction(%.6f)", value);
+        currentValue = value;
+    }
+
+    void DrawText(SkCanvas *canvas, SkPaint *paint, const char text[]) {
+        float spacing = 0.05;
+        for (int i = 0; i < sizeof(text) / sizeof(text[0]); i++) {
+            const char letter[] = {text[i]};
+            size_t byteLength = strlen(static_cast<const char *>(letter));
+            canvas->drawText(letter, byteLength, spacing * i, 0, *paint);
+        }
+    }
+
+    void DrawAxes(SkCanvas *canvas, SkMatrix44 m) {
+        SkPaint p;
+        p.setStrokeWidth(10);
+        SkPoint3 src[4] = {
+                {0,   0,   0},
+                {0.2, 0,   0},
+                {0,   0.2, 0},
+                {0,   0,   0.2},
+        };
+        SkPoint dst[4];
+        Sk3MapPts(dst, m, src, 4);
+
+        const char str[] = "XYZ";
+        p.setColor(SK_ColorRED);
+        canvas->drawLine(dst[0], dst[1], p);
+
+        p.setColor(SK_ColorGREEN);
+        canvas->drawLine(dst[0], dst[2], p);
+
+        p.setColor(SK_ColorBLUE);
+        canvas->drawLine(dst[0], dst[3], p);
+    }
+
+    void DrawVector(SkCanvas *canvas, SkMatrix44 m, glm::vec3 begin, glm::vec3 end, SkColor c) {
+        SkPaint p;
+        p.setStrokeWidth(15);
+        SkPoint3 src[2] = {
+                {begin.x, begin.y, begin.z},
+                {end.x,   end.y,   end.z}
+        };
+        SkPoint dst[2];
+        Sk3MapPts(dst, m, src, 2);
+
+        const char str[] = "XYZ";
+        p.setColor(c);
+        canvas->drawLine(dst[0], dst[1], p);
+    }
+
+    void DrawBoundingBox(SkCanvas* canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorYELLOW);
+        SkIRect bounds = canvas->getDeviceClipBounds();
+        SkRect b = SkRect::Make(bounds);
+
+        canvas->drawRect(b, paint);
+    }
+
+    void HelloArApplication::OnDrawFrame() {
+        grContext = GrContext::MakeGL();
+
+        GrBackendRenderTarget target;
+        sk_sp<SkSurface> surface = nullptr;
+        GrGLFramebufferInfo framebuffer_info;
+        framebuffer_info.fFBOID = 0;
+        framebuffer_info.fFormat = 0x8058;
+
+
+        glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
+        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
+
+        glEnable(GL_CULL_FACE);
+        glEnable(GL_DEPTH_TEST);
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+        if (ar_session_ == nullptr) return;
+
+        ArSession_setCameraTextureName(ar_session_,
+                                       background_renderer_.GetTextureId());
+
+        // Update session to get current frame and render camera background.
+        if (ArSession_update(ar_session_, ar_frame_) != AR_SUCCESS) {
+            LOGE("HelloArApplication::OnDrawFrame ArSession_update error");
+        }
+
+        // GET CAMERA INFO
+        ArCamera *ar_camera;
+        ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera);
+
+        glm::mat4 view_mat;
+        glm::mat4 projection_mat;
+        ArCamera_getViewMatrix(ar_session_, ar_camera, glm::value_ptr(view_mat));
+        ArCamera_getProjectionMatrix(ar_session_, ar_camera,
+                /*near=*/0.1f, /*far=*/100.f,
+                                     glm::value_ptr(projection_mat));
+
+        ArTrackingState camera_tracking_state;
+        ArCamera_getTrackingState(ar_session_, ar_camera, &camera_tracking_state);
+        ArCamera_release(ar_camera);
+
+        background_renderer_.Draw(ar_session_, ar_frame_);
+
+        // If the camera isn't tracking don't bother rendering other objects.
+        if (camera_tracking_state != AR_TRACKING_STATE_TRACKING) {
+            return;
+        }
+
+        // Get light estimation value.
+        ArLightEstimate *ar_light_estimate;
+        ArLightEstimateState ar_light_estimate_state;
+        ArLightEstimate_create(ar_session_, &ar_light_estimate);
+
+        ArFrame_getLightEstimate(ar_session_, ar_frame_, ar_light_estimate);
+        ArLightEstimate_getState(ar_session_, ar_light_estimate,
+                                 &ar_light_estimate_state);
+
+        // Set light intensity to default. Intensity value ranges from 0.0f to 1.0f.
+        // The first three components are color scaling factors.
+        // The last one is the average pixel intensity in gamma space.
+        float color_correction[4] = {1.f, 1.f, 1.f, 1.f};
+        if (ar_light_estimate_state == AR_LIGHT_ESTIMATE_STATE_VALID) {
+            ArLightEstimate_getColorCorrection(ar_session_, ar_light_estimate,
+                                               color_correction);
+        }
+
+        ArLightEstimate_destroy(ar_light_estimate);
+        ar_light_estimate = nullptr;
+        SkMatrix44 skProj;
+        SkMatrix44 skView;
+        SkMatrix skViewport;
+
+        skProj = util::GlmMatToSkMat(projection_mat);
+        skView = util::GlmMatToSkMat(view_mat);
+        skViewport.setScale(width_ / 2, -height_ / 2);
+        skViewport.postTranslate(width_ / 2, height_ / 2);
+        target = GrBackendRenderTarget(width_, height_, 0, 0, framebuffer_info);
+        surface = SkSurface::MakeFromBackendRenderTarget(grContext.get(),
+                                                         target,
+                                                         kBottomLeft_GrSurfaceOrigin,
+                                                         kRGBA_8888_SkColorType,
+                                                         nullptr, nullptr);
+
+        // Render Andy objects.
+        std::vector<SkMatrix44> models;
+        //glm::mat4 model_mat(1.0f);
+        for (const auto &obj_iter : tracked_obj_set_) {
+            ArTrackingState tracking_state = AR_TRACKING_STATE_STOPPED;
+            ArAnchor_getTrackingState(ar_session_, obj_iter, &tracking_state);
+            if (tracking_state == AR_TRACKING_STATE_TRACKING) {
+                // Render object only if the tracking state is AR_TRACKING_STATE_TRACKING.
+                //util::GetTransformMatrixFromAnchor(ar_session_, obj_iter, &model_mat);
+                //DRAW ANDY
+                //andy_renderer_.Draw(glm::mat4(1), glm::mat4(1), model_mat, color_correction);
+
+                //PREPARE SKIA MATS
+
+                SkMatrix44 skModel;
+
+                switch (currentObjectRotation) {
+                    case 0: {
+                        auto iter = anchor_skmat4_axis_aligned_map_.find(obj_iter);
+                        if (iter != anchor_skmat4_axis_aligned_map_.end()) {
+                            skModel = iter->second;
+                            models.push_back(skModel);
+                        }
+                    }
+                        break;
+                    case 1: {
+                        auto iter = anchor_skmat4_camera_aligned_map_.find(obj_iter);
+                        if (iter != anchor_skmat4_camera_aligned_map_.end()) {
+                            skModel = iter->second;
+                            models.push_back(skModel);
+                        }
+                    }
+                        break;
+                    case 2: {
+                        auto iter = anchor_skmat4_snap_aligned_map_.find(obj_iter);
+                        if (iter != anchor_skmat4_snap_aligned_map_.end()) {
+                            skModel = iter->second;
+                            models.push_back(skModel);
+                        }
+                    }
+                        break;
+                    default: {
+                        auto iter = anchor_skmat4_axis_aligned_map_.find(obj_iter);
+                        if (iter != anchor_skmat4_axis_aligned_map_.end()) {
+                            skModel = iter->second;
+                            models.push_back(skModel);
+                        }
+                    }
+                        break;
+                }
+
+            }
+        }
+
+        // Update and render planes.
+        ArTrackableList *plane_list = nullptr;
+        ArTrackableList_create(ar_session_, &plane_list);
+        CHECK(plane_list != nullptr);
+
+        ArTrackableType plane_tracked_type = AR_TRACKABLE_PLANE;
+        ArSession_getAllTrackables(ar_session_, plane_tracked_type, plane_list);
+
+        int32_t plane_list_size = 0;
+        ArTrackableList_getSize(ar_session_, plane_list, &plane_list_size);
+        plane_count_ = plane_list_size;
+
+        for (int i = 0; i < plane_list_size; ++i) {
+            ArTrackable *ar_trackable = nullptr;
+            ArTrackableList_acquireItem(ar_session_, plane_list, i, &ar_trackable);
+            ArPlane *ar_plane = ArAsPlane(ar_trackable);
+            ArTrackingState out_tracking_state;
+            ArTrackable_getTrackingState(ar_session_, ar_trackable,
+                                         &out_tracking_state);
+
+            ArPlane *subsume_plane;
+            ArPlane_acquireSubsumedBy(ar_session_, ar_plane, &subsume_plane);
+            if (subsume_plane != nullptr) {
+                ArTrackable_release(ArAsTrackable(subsume_plane));
+                continue;
+            }
+
+            if (ArTrackingState::AR_TRACKING_STATE_TRACKING != out_tracking_state) {
+                continue;
+            }
+
+            ArTrackingState plane_tracking_state;
+            ArTrackable_getTrackingState(ar_session_, ArAsTrackable(ar_plane),
+                                         &plane_tracking_state);
+            if (plane_tracking_state == AR_TRACKING_STATE_TRACKING) {
+                const auto iter = plane_color_map_.find(ar_plane);
+                glm::vec3 color;
+                if (iter != plane_color_map_.end()) {
+                    color = iter->second;
+
+                    // If this is an already observed trackable release it so it doesn't
+                    // leave aof placing objects on surfaces (n additional reference dangling.
+                    ArTrackable_release(ar_trackable);
+                } else {
+                    // The first plane is always white.
+                    if (!first_plane_has_been_found_) {
+                        first_plane_has_been_found_ = true;
+                        color = kWhite;
+                    } else {
+                        color = GetRandomPlaneColor();
+                    }
+                    plane_color_map_.insert({ar_plane, color});
+                }
+
+                plane_renderer_.Draw(projection_mat, view_mat, ar_session_, ar_plane,
+                                     color);
+            }
+        }
+
+        ArTrackableList_destroy(plane_list);
+        plane_list = nullptr;
+
+        // Update and render point cloud.
+        ArPointCloud *ar_point_cloud = nullptr;
+        ArStatus point_cloud_status =
+                ArFrame_acquirePointCloud(ar_session_, ar_frame_, &ar_point_cloud);
+        if (point_cloud_status == AR_SUCCESS) {
+            point_cloud_renderer_.Draw(projection_mat * view_mat, ar_session_,
+                                       ar_point_cloud);
+            ArPointCloud_release(ar_point_cloud);
+        }
+        SkMatrix44 i = SkMatrix44::kIdentity_Constructor;
+
+        if (surface != nullptr) {
+            SkCanvas *canvas = surface->getCanvas();
+            SkAutoCanvasRestore acr(canvas, true);
+            SkMatrix44 vpv = skViewport * skProj * skView;
+            for(SkMatrix44 skModel: models) {
+                SkMatrix44 i = SkMatrix44::kIdentity_Constructor;
+                canvas->setMatrix(i);
+                SkMatrix44 mvpv = skViewport * skProj * skView * skModel;
+
+                //Draw XYZ axes
+                DrawAxes(canvas, mvpv);
+                //Drawing camera orientation
+            /*	DrawVector(canvas, vpv, begins[0], ends[0], SK_ColorMAGENTA);
+                DrawVector(canvas, vpv, begins[0], ends[1], SK_ColorYELLOW);
+                DrawVector(canvas, vpv, begins[0], ends[2], SK_ColorCYAN);*/
+
+                canvas->concat(mvpv);
+                SkPaint paint;
+
+                //Draw Circle
+                paint.setColor(0x80700000);
+                canvas->drawCircle(0, 0, 0.1, paint);
+
+                //Draw Text
+                paint.setColor(SK_ColorBLUE);
+                if (currentValue != 0) {
+                    paint.setTextSize(currentValue);
+                } else {
+                    paint.setTextSize(0.1);
+                }
+
+                paint.setAntiAlias(true);
+                const char text[] = "SkAR";
+                size_t byteLength = strlen(static_cast<const char *>(text));
+                SkShaper shaper(nullptr);
+                SkTextBlobBuilder builder;
+                SkPoint p = SkPoint::Make(0, 0);
+                shaper.shape(&builder, paint, text, byteLength, true, p, 10);
+                canvas->drawTextBlob(builder.make(), 0, 0, paint);
+
+                //DrawBoundingBox(canvas);
+            }
+            canvas->flush();
+        }
+    }
+
+
+    bool HelloArApplication::OnTouchedFirst(float x, float y, int drawMode) {
+        LOGI("Entered OnTouchedFirst");
+        if (pendingAnchor != nullptr) {
+            delete pendingAnchor;
+        }
+        SkPoint p = SkPoint::Make(x,y);
+        pendingAnchor = new PendingAnchor(p);
+        bool editAnchor = false;
+
+        if (ar_frame_ != nullptr && ar_session_ != nullptr) {
+            ArHitResultList *hit_result_list = nullptr;
+            ArHitResultList_create(ar_session_, &hit_result_list);
+            CHECK(hit_result_list);
+            ArFrame_hitTest(ar_session_, ar_frame_, x, y, hit_result_list);
+
+            int32_t hit_result_list_size = 0;
+            ArHitResultList_getSize(ar_session_, hit_result_list, &hit_result_list_size);
+            ArHitResult *ar_hit_result = nullptr;
+            ArPose *out_pose = nullptr;
+            ArPlane* hitPlane = nullptr;
+            for (int32_t i = 0; i < hit_result_list_size; ++i) {
+                ArHitResult *ar_hit = nullptr;
+                ArPose *created_out_pose = nullptr;
+                ArHitResult_create(ar_session_, &ar_hit);
+                ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit);
+
+                if (ar_hit == nullptr) {
+                    LOGE("HelloArApplication::OnTouched ArHitResultList_getItem error");
+                    return editAnchor;
+                }
+
+                ArTrackable *ar_trackable = nullptr;
+                ArHitResult_acquireTrackable(ar_session_, ar_hit, &ar_trackable);
+                ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID;
+                ArTrackable_getType(ar_session_, ar_trackable, &ar_trackable_type);
+                // Creates an anchor if a plane or an oriented point was hit.
+                if (AR_TRACKABLE_PLANE == ar_trackable_type) {
+                    ArPose *hit_pose = nullptr;
+                    ArPose_create(ar_session_, nullptr, &hit_pose);
+                    ArHitResult_getHitPose(ar_session_, ar_hit, hit_pose);
+                    int32_t in_polygon = 0;
+                    ArPlane *ar_plane = ArAsPlane(ar_trackable);
+                    ArPlane_isPoseInPolygon(ar_session_, ar_plane, hit_pose, &in_polygon);
+
+                    {
+                        // Use hit pose and camera pose to check if hittest is from the
+                        // back of the plane, if it is, no need to create the anchor.
+                        ArPose *camera_pose = nullptr;
+                        ArPose_create(ar_session_, nullptr, &camera_pose);
+                        ArCamera *ar_camera;
+                        ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera);
+                        ArCamera_getPose(ar_session_, ar_camera, camera_pose);
+                        float normal_distance_to_plane = util::CalculateDistanceToPlane(
+                                ar_session_, *hit_pose, *camera_pose);
+
+                        if (!in_polygon || normal_distance_to_plane < 0) {
+                            ArPose_destroy(camera_pose);
+                            continue;
+                        }
+                        ArPose_destroy(camera_pose);
+                        ArCamera_release(ar_camera);
+                    }
+
+                    //Raw pose of hit location
+                    float out_hit_raw[] = {0, 0, 0, 0, 0, 0, 0};
+                    ArPose_getPoseRaw(ar_session_, hit_pose, out_hit_raw);
+                    ArPose_destroy(hit_pose);
+
+                    //Position of anchor
+                    glm::vec4 pendingAnchorPos(out_hit_raw[4], out_hit_raw[5], out_hit_raw[6], 1);
+                    pendingAnchor->SetContainingPlane(ar_plane);
+
+                    //Check if plane contains approx the same anchor
+                    auto planeAnchors = plane_anchors_map_.find(ar_plane);
+                    if (planeAnchors != plane_anchors_map_.end()) {
+                        //other anchors existed on this plane
+                        std::vector<ArAnchor*> anchors = planeAnchors->second;
+                        int i = 0;
+                        LOGI("Size of anchor list: %d", (int) anchors.size());
+                        for(ArAnchor* const& anchor: anchors) {
+                            //Get anchor's pose
+                            i++;
+                            LOGI("CHECKING: Anchor #%d", i);
+                            ArPose *anchor_pose = nullptr;
+                            ArPose_create(ar_session_, nullptr, &anchor_pose);
+                            ArAnchor_getPose(ar_session_, anchor, anchor_pose);
+                            float out_anchor_raw[] = {0, 0, 0, 0, 0, 0, 0};
+                            ArPose_getPoseRaw(ar_session_, anchor_pose, out_anchor_raw);
+                            ArPose_destroy(anchor_pose);
+                            glm::vec4 oldAnchorPos(out_anchor_raw[4], out_anchor_raw[5], out_anchor_raw[6], 1);
+                            oldAnchorPos = oldAnchorPos - pendingAnchorPos;
+                            float distance = util::Magnitude(glm::vec3(oldAnchorPos));
+                            if (distance < 0.1f) {
+                                LOGI("TouchFirst: Editing old anchor!");
+                                editAnchor = true;
+                                pendingAnchor->SetArAnchor(anchor);
+                                pendingAnchor->SetEditMode(true);
+
+                                ArHitResult_destroy(ar_hit);
+                                ArHitResultList_destroy(hit_result_list);
+                                LOGI("TouchFirst: Edit %d", editAnchor);
+                                return editAnchor;
+                            }
+                        }
+                    }
+
+                    //actual hit result, and containing plane
+                    ar_hit_result = ar_hit;
+                    hitPlane = ar_plane;
+
+                    //new anchor pos
+                    float wanted_raw_pose[] = {0, 0, 0, 0, out_hit_raw[4], out_hit_raw[5], out_hit_raw[6]};
+                    ArPose_create(ar_session_, wanted_raw_pose, &created_out_pose);
+                    out_pose = created_out_pose;
+                    break;
+                }
+            }
+
+
+            if (ar_hit_result) {
+                LOGI("TouchFirst: Adding new anchor!");
+                ArAnchor *anchor = nullptr;
+                pendingAnchor->SetEditMode(false);
+
+                if (ArSession_acquireNewAnchor(ar_session_, out_pose, &anchor) != AR_SUCCESS) {
+                    LOGE("HelloArApplication::OnTouched ArHitResult_acquireNewAnchor error");
+                    LOGI("TouchFirst: Failed to acquire new anchor");
+                    delete hitPlane;
+                    delete pendingAnchor;
+                    pendingAnchor = nullptr;
+                    LOGI("TouchFirst: Edit %d", editAnchor);
+                    return editAnchor;
+                }
+                pendingAnchor->SetArAnchor(anchor);
+
+                ArHitResult_destroy(ar_hit_result);
+                ArHitResultList_destroy(hit_result_list);
+                ArPose_destroy(out_pose);
+                hit_result_list = nullptr;
+                LOGI("TouchFirst: Edit %d", editAnchor);
+                return editAnchor;
+            }
+
+            LOGI("TouchFirst: didn't hit anything");
+            delete hitPlane;
+            delete pendingAnchor;
+            pendingAnchor = nullptr;
+            LOGI("TouchFirst: Edit %d", editAnchor);
+            return editAnchor;
+        }
+    }
+
+    void HelloArApplication::AddAnchor(ArAnchor* anchor, ArPlane* containingPlane) {
+        //delete anchor from matrices maps
+        //releasing the anchor if it is not tracking anymore
+        ArTrackingState tracking_state = AR_TRACKING_STATE_STOPPED;
+        ArAnchor_getTrackingState(ar_session_, anchor, &tracking_state);
+        if (tracking_state != AR_TRACKING_STATE_TRACKING) {
+            RemoveAnchor(anchor);
+            return;
+        }
+
+        //releasing the first anchor if we exceeded maximum number of objects to be rendered
+        if (tracked_obj_set_.size() >= kMaxNumberOfAndroidsToRender) {
+            RemoveAnchor(tracked_obj_set_[0]);
+        }
+
+        //updating the containing plane with a new anchor
+        auto planeAnchors = plane_anchors_map_.find(containingPlane);
+        if (planeAnchors != plane_anchors_map_.end()) {
+            //other anchors existed on this plane
+            LOGI("TouchFinal: ADDING TO OLD ANCHORS");
+            std::vector<ArAnchor*> anchors = planeAnchors->second;
+            anchors.push_back(anchor);
+            plane_anchors_map_[containingPlane] = anchors;
+            anchor_plane_map_.insert({anchor, containingPlane});
+        } else {
+            LOGI("TouchFinal: NEW SET OF ANCHORS");
+            std::vector<ArAnchor*> anchors;
+            anchors.push_back(anchor);
+            plane_anchors_map_.insert({containingPlane, anchors});
+            anchor_plane_map_.insert({anchor, containingPlane});
+        }
+
+        tracked_obj_set_.push_back(anchor);
+    }
+
+    void HelloArApplication::OnTouchTranslate(float x, float y) {
+        LOGI("Entered On Edit Touched");
+        ArAnchor *anchor = pendingAnchor->GetArAnchor();
+        glm::mat4 matrix = util::SkMatToGlmMat(
+                anchor_skmat4_axis_aligned_map_.find(anchor)->second);
+
+        if (ar_frame_ != nullptr && ar_session_ != nullptr) {
+            ArHitResultList *hit_result_list = nullptr;
+            ArHitResultList_create(ar_session_, &hit_result_list);
+            CHECK(hit_result_list);
+            ArFrame_hitTest(ar_session_, ar_frame_, x, y, hit_result_list);
+
+            int32_t hit_result_list_size = 0;
+            ArHitResultList_getSize(ar_session_, hit_result_list, &hit_result_list_size);
+            ArHitResult *ar_hit_result = nullptr;
+            ArPose *out_pose = nullptr;
+            ArPlane *hitPlane = nullptr;
+            for (int32_t i = 0; i < hit_result_list_size; ++i) {
+                ArHitResult *ar_hit = nullptr;
+                ArPose *created_out_pose = nullptr;
+                ArHitResult_create(ar_session_, &ar_hit);
+                ArHitResultList_getItem(ar_session_, hit_result_list, i, ar_hit);
+
+                if (ar_hit == nullptr) {
+                    LOGE("HelloArApplication::OnTouched ArHitResultList_getItem error");
+                    return;
+                }
+
+                ArTrackable *ar_trackable = nullptr;
+                ArHitResult_acquireTrackable(ar_session_, ar_hit, &ar_trackable);
+                ArTrackableType ar_trackable_type = AR_TRACKABLE_NOT_VALID;
+                ArTrackable_getType(ar_session_, ar_trackable, &ar_trackable_type);
+                // Creates an anchor if a plane or an oriented point was hit.
+                if (AR_TRACKABLE_PLANE == ar_trackable_type) {
+                    ArPose *hit_pose = nullptr;
+                    ArPose_create(ar_session_, nullptr, &hit_pose);
+                    ArHitResult_getHitPose(ar_session_, ar_hit, hit_pose);
+                    int32_t in_polygon = 0;
+                    ArPlane *ar_plane = ArAsPlane(ar_trackable);
+                    ArPlane_isPoseInPolygon(ar_session_, ar_plane, hit_pose, &in_polygon);
+
+                    {
+                        // Use hit pose and camera pose to check if hittest is from the
+                        // back of the plane, if it is, no need to create the anchor.
+                        ArPose *camera_pose = nullptr;
+                        ArPose_create(ar_session_, nullptr, &camera_pose);
+                        ArCamera *ar_camera;
+                        ArFrame_acquireCamera(ar_session_, ar_frame_, &ar_camera);
+                        ArCamera_getPose(ar_session_, ar_camera, camera_pose);
+                        float normal_distance_to_plane = util::CalculateDistanceToPlane(
+                                ar_session_, *hit_pose, *camera_pose);
+
+                        if (!in_polygon || normal_distance_to_plane < 0) {
+                            ArPose_destroy(camera_pose);
+                            continue;
+                        }
+                        ArPose_destroy(camera_pose);
+                        ArCamera_release(ar_camera);
+                    }
+
+                    //Raw pose of hit location
+                    float out_hit_raw[] = {0, 0, 0, 0, 0, 0, 0};
+                    ArPose_getPoseRaw(ar_session_, hit_pose, out_hit_raw);
+                    ArPose_destroy(hit_pose);
+
+                    //Translate by new amount
+                    glm::vec4 newPos(out_hit_raw[4], out_hit_raw[5], out_hit_raw[6], 1);
+                    glm::vec4 oldPos = pendingAnchor->GetAnchorPos(ar_session_);
+                    glm::vec3 movement = glm::vec3(newPos - oldPos);
+
+
+                    //CAMERA SETTINGS
+                    glm::mat4 backToOrigin(1);
+                    backToOrigin = glm::translate(backToOrigin, -glm::vec3(oldPos));
+                    glm::mat4 backToPlane(1);
+                    backToPlane = glm::translate(backToPlane, glm::vec3(oldPos));
+
+                    //Axes of Skia object: start with XYZ, totate to get X(-Z)Y, paste on plane, go back to origin --> plane orientation but on origin
+                    glm::vec3 objX = glm::normalize(glm::vec3(
+                            backToOrigin * matrix *
+                            glm::vec4(1, 0, 0, 1))); //X still X
+                    glm::vec3 objY = glm::normalize(glm::vec3(
+                            backToOrigin * matrix *
+                            glm::vec4(0, 1, 0, 1))); //Y is now Z
+                    glm::vec3 objZ = glm::normalize(glm::vec3(
+                            backToOrigin * matrix *
+                            glm::vec4(0, 0, 1, 1))); //Z is now Y
+
+
+                    glm::mat4 translate(1);
+                    translate = glm::translate(translate, movement);
+                    matrix = translate * matrix;
+                    RemoveAnchor(anchor);
+
+
+
+                    //new anchor pos
+                    float wanted_raw_pose[] = {0, 0, 0, 0, out_hit_raw[4], out_hit_raw[5],
+                                               out_hit_raw[6]};
+                    ArPose_create(ar_session_, wanted_raw_pose, &created_out_pose);
+                    out_pose = created_out_pose;
+                    ar_hit_result = ar_hit;
+                    break;
+                }
+            }
+
+            if (ar_hit_result) {
+                LOGI("TouchFirst: Adding new anchor!");
+                ArAnchor *anchor = nullptr;
+                pendingAnchor->SetEditMode(false);
+
+                if (ArSession_acquireNewAnchor(ar_session_, out_pose, &anchor) != AR_SUCCESS) {
+                    LOGE("HelloArApplication::OnTouched ArHitResult_acquireNewAnchor error");
+                    LOGI("TouchFirst: Failed to acquire new anchor");
+                    delete hitPlane;
+                    delete pendingAnchor;
+                    pendingAnchor = nullptr;
+                    return;
+                }
+                pendingAnchor->SetArAnchor(anchor);
+                anchor_skmat4_axis_aligned_map_[anchor] = util::GlmMatToSkMat(matrix);
+
+                //Add anchor
+                AddAnchor(anchor, pendingAnchor->GetContainingPlane());
+
+
+                ArHitResult_destroy(ar_hit_result);
+                ArHitResultList_destroy(hit_result_list);
+                ArPose_destroy(out_pose);
+                hit_result_list = nullptr;
+                return;
+            }
+        }
+    }
+
+    void HelloArApplication::RemoveAnchor(ArAnchor* anchor) {
+        //delete anchor from matrices maps
+        anchor_skmat4_axis_aligned_map_.erase(anchor);
+        anchor_skmat4_camera_aligned_map_.erase(anchor);
+        anchor_skmat4_snap_aligned_map_.erase(anchor);
+
+        auto containingPlaneIter = anchor_plane_map_.find(anchor);
+        if (containingPlaneIter != anchor_plane_map_.end()) {
+            ArPlane*  containingPlane = containingPlaneIter->second;
+            auto planeAnchors = plane_anchors_map_.find(containingPlane);
+            if (planeAnchors != plane_anchors_map_.end()) {
+                //delete this anchor from the list of anchors associated with its plane
+                std::vector<ArAnchor*> anchors = planeAnchors->second;
+                anchors.erase(std::remove(anchors.begin(), anchors.end(), anchor), anchors.end());
+                plane_anchors_map_[planeAnchors->first] = anchors;
+
+                //delete anchor from map of anchor to plane
+                anchor_plane_map_.erase(anchor);
+            }
+        }
+        //delete anchor from list of tracked objects
+        tracked_obj_set_.erase(std::remove(tracked_obj_set_.begin(), tracked_obj_set_.end(), anchor), tracked_obj_set_.end());
+        ArAnchor_release(anchor);
+    }
+
+    void HelloArApplication::UpdateMatrixMaps(ArAnchor* anchorKey, glm::mat4 aaMat, glm::mat4 caMat, glm::mat4 snapMat) {
+        anchor_skmat4_axis_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(aaMat)});
+        anchor_skmat4_camera_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(caMat)});
+        anchor_skmat4_snap_aligned_map_.insert({anchorKey, util::GlmMatToSkMat(snapMat)});
+    }
+
+    void SetSkiaInitialRotation(glm::mat4& initRotation) {
+        initRotation = glm::rotate(initRotation, SK_ScalarPI / 2, glm::vec3(1, 0, 0));
+    }
+
+    void SetSkiaObjectAxes(glm::vec3& x, glm::vec3& y, glm::vec3& z, glm::mat4 transform) {
+        x = glm::normalize(glm::vec3(transform * glm::vec4(1, 0, 0, 1))); //X still X
+        y = glm::normalize(glm::vec3(transform  * glm::vec4(0, 1, 0, 1))); //Y is now Z
+        z = glm::normalize(glm::vec3(transform  * glm::vec4(0, 0, 1, 1))); //Z is now Y
+    }
+
+    void SetCameraAlignedRotation(glm::mat4& rotateTowardsCamera, float& rotationDirection, const glm::vec3& toProject, const glm::vec3& skiaY, const glm::vec3& skiaZ) {
+        glm::vec3 hitLookProj = -util::ProjectOntoPlane(toProject, skiaZ);
+        float angleRad = util::AngleRad(skiaY, hitLookProj);
+        glm::vec3 cross = glm::normalize(glm::cross(skiaY, hitLookProj));
+
+        //outs
+        rotationDirection = util::Dot(cross, skiaZ);
+        rotateTowardsCamera = glm::rotate(rotateTowardsCamera, angleRad, rotationDirection * skiaZ);
+    }
+
+    struct CameraAlignmentInfo {
+        glm::vec3& skiaY, skiaZ;
+        glm::mat4& preRot, postRot;
+
+        CameraAlignmentInfo(glm::vec3& skiaY, glm::vec3& skiaZ, glm::mat4 preRot, glm::mat4 postRot)
+                : skiaY(skiaY), skiaZ(skiaZ), preRot(preRot), postRot(postRot) {}
+    };
+
+    void SetCameraAlignedVertical(glm::mat4& caMat, const glm::mat4& camRot, const CameraAlignmentInfo& camAlignInfo) {
+        //Camera axes
+        glm::vec3 xCamera = glm::vec3(glm::vec4(1, 0, 0, 1) * camRot);
+        glm::vec3 yCamera = glm::vec3(glm::vec4(0, 1, 0, 1) * camRot);
+        glm::vec3 zCamera = glm::vec3(glm::vec4(0, 0, -1, 1) * camRot);
+
+        //Get matrix that rotates object from plane towards the wanted angle
+        glm::mat4 rotateTowardsCamera(1);
+        float rotationDirection = 1;
+        SetCameraAlignedRotation(rotateTowardsCamera, rotationDirection, yCamera, camAlignInfo.skiaY, camAlignInfo.skiaZ);
+
+        //LogOrientation(dot, angleRad, "Vertical/Wall");
+        glm::mat4 flip(1);
+        flip = glm::rotate(flip, SK_ScalarPI, rotationDirection * camAlignInfo.skiaZ);
+        caMat = camAlignInfo.postRot * flip * rotateTowardsCamera * camAlignInfo.preRot;
+    }
+
+    void SetCameraAlignedHorizontal(glm::mat4& caMat, ArPlaneType planeType, const glm::vec3 hitLook, const CameraAlignmentInfo& camAlignInfo) {
+        //Ceiling or Floor: follow hit location
+        //Get matrix that rotates object from plane towards the wanted angle
+        glm::mat4 rotateTowardsCamera(1);
+        float rotationDirection = 1;
+        SetCameraAlignedRotation(rotateTowardsCamera, rotationDirection, hitLook, camAlignInfo.skiaY, camAlignInfo.skiaZ);
+
+        if (planeType == ArPlaneType::AR_PLANE_HORIZONTAL_DOWNWARD_FACING) {
+            //ceiling
+            //LogOrientation(dot, angleRad, "Ceiling");
+            glm::mat4 flip(1);
+            flip = glm::rotate(flip, SK_ScalarPI, rotationDirection * camAlignInfo.skiaZ);
+            caMat = camAlignInfo.postRot * flip * rotateTowardsCamera * camAlignInfo.preRot;
+        } else {
+            //floor or tabletop
+            //LogOrientation(dot, angleRad, "Floor");
+            caMat = camAlignInfo.postRot * rotateTowardsCamera * camAlignInfo.preRot;
+        }
+    }
+
+
+
+    void HelloArApplication::SetCameraAlignedMatrix(glm::mat4& caMat, glm::vec3 hitPos, glm::mat4& planeModel, const glm::mat4& initRotation) {
+        //Translation matrices: from plane to origin, and from origin to plane
+        glm::mat4 backToOrigin(1);
+        backToOrigin = glm::translate(backToOrigin, -hitPos);
+        glm::mat4 backToPlane(1);
+        backToPlane = glm::translate(backToPlane, hitPos);
+
+        //Axes of Skia object: start with XYZ, totate to get X(-Z)Y, paste on plane, go back to origin --> plane orientation but on origin
+        glm::vec3 skiaX, skiaY, skiaZ;
+        SetSkiaObjectAxes(skiaX, skiaY, skiaZ, backToOrigin * planeModel * initRotation);
+
+        //Get camera position & rotation
+        glm::vec3 cameraPos;
+        glm::mat4 cameraRotationMatrix;
+        util::GetCameraInfo(ar_session_, ar_frame_, cameraPos, cameraRotationMatrix);
+
+        //Set matrix depending on type of surface
+        ArPlaneType planeType = AR_PLANE_VERTICAL;
+        ArPlane_getType(ar_session_, pendingAnchor->GetContainingPlane(), &planeType);
+
+        //Set CamerAlignmentInfo
+        CameraAlignmentInfo camAlignInfo(skiaY, skiaZ, backToOrigin * planeModel * initRotation, backToPlane);
+
+        if (planeType == ArPlaneType::AR_PLANE_VERTICAL) {
+            //Wall: follow phone orientation
+            SetCameraAlignedVertical(caMat, cameraRotationMatrix, camAlignInfo);
+        } else {
+            //Ceiling or Floor: follow hit location
+            glm::vec3 hitLook(hitPos - cameraPos);
+            SetCameraAlignedHorizontal(caMat, planeType, hitLook, camAlignInfo);
+        }
+    }
+
+
+    void HelloArApplication::SetModelMatrices(glm::mat4& aaMat, glm::mat4& caMat, glm::mat4& snapMat, const glm::mat4& planeModel) {
+        //Brings Skia world to ARCore world
+        glm::mat4 initRotation(1);
+        SetSkiaInitialRotation(initRotation);
+
+        //Copy plane model for editing
+        glm::mat4 copyPlaneModel(planeModel);
+
+        //Set snap matrix
+        //snapMat = copyPlaneModel * initRotation;
+
+        //Set axis-aligned matrix
+        glm::vec4 anchorPos = pendingAnchor->GetAnchorPos(ar_session_);
+        copyPlaneModel[3] = anchorPos;
+        aaMat = planeModel * initRotation;
+
+        //Set camera-aligned matrix
+        //SetCameraAlignedMatrix(caMat, glm::vec3(anchorPos), copyPlaneModel, initRotation);
+    }
+
+    void GetPlaneModelMatrix(glm::mat4& planeModel, ArSession* arSession, ArPlane* arPlane) {
+        ArPose *plane_pose = nullptr;
+        ArPose_create(arSession, nullptr, &plane_pose);
+        ArPlane_getCenterPose(arSession, arPlane, plane_pose);
+        util::GetTransformMatrixFromPose(arSession, plane_pose, &planeModel);
+        ArPose_destroy(plane_pose);
+    }
+
+    void HelloArApplication::OnTouchedFinal(int type) {
+        LOGI("Entered OnTouchedFinal");
+        if (pendingAnchor == nullptr) {
+            LOGI("WARNING: Entered OnTouchedFinal but no pending anchor..");
+            return;
+        }
+
+        if (pendingAnchor->GetEditMode()) {
+            LOGI("WARNING: Editing old anchor in OnTouchedFinal!");
+        }
+
+        //Get necessary pending anchor info
+        ArPlane* containingPlane = pendingAnchor->GetContainingPlane();
+        glm::vec4 pendingAnchorPos = pendingAnchor->GetAnchorPos(ar_session_);
+        ArAnchor* actualAnchor = pendingAnchor->GetArAnchor();
+
+        //Plane model matrix
+        glm::mat4 planeModel(1);
+        GetPlaneModelMatrix(planeModel, ar_session_, containingPlane);
+
+        //Setup skia object model matrices
+        glm::mat4 matrixAxisAligned(1);
+        glm::mat4 matrixCameraAligned(1);
+        glm::mat4 matrixSnapAligned(1);
+        SetModelMatrices(matrixAxisAligned, matrixCameraAligned, matrixSnapAligned, planeModel);
+
+        //Update anchor -> model matrix datastructures
+        UpdateMatrixMaps(actualAnchor, matrixAxisAligned, matrixCameraAligned, matrixSnapAligned);
+
+        //Add anchor to aux datastructures
+        AddAnchor(actualAnchor, containingPlane);
+    }
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h
new file mode 100644
index 0000000..2ebec7e
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/hello_ar_application.h
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_
+#define C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <android/asset_manager.h>
+#include <jni.h>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <GrContext.h>
+#include <gl/GrGLTypes.h>
+#include <GrBackendSurface.h>
+#include <SkSurface.h>
+#include <Skottie.h>
+
+#include "arcore_c_api.h"
+#include "background_renderer.h"
+#include "glm.h"
+#include "plane_renderer.h"
+#include "point_cloud_renderer.h"
+#include "util.h"
+#include "pending_anchor.h"
+
+namespace hello_ar {
+
+// HelloArApplication handles all application logics.
+    class HelloArApplication {
+    public:
+        // Constructor and deconstructor.
+        HelloArApplication() = default;
+
+        HelloArApplication(AAssetManager *asset_manager);
+
+        ~HelloArApplication();
+
+        SkMatrix SkiaRenderer(const glm::mat4 &proj, const glm::mat4 &view, const glm::mat4 &model);
+
+        // OnPause is called on the UI thread from the Activity's onPause method.
+        void OnPause();
+
+        // OnResume is called on the UI thread from the Activity's onResume method.
+        void OnResume(void *env, void *context, void *activity);
+
+        // OnSurfaceCreated is called on the OpenGL thread when GLSurfaceView
+        // is created.
+        void OnSurfaceCreated();
+
+        // OnDisplayGeometryChanged is called on the OpenGL thread when the
+        // render surface size or display rotation changes.
+        //
+        // @param display_rotation: current display rotation.
+        // @param width: width of the changed surface view.
+        // @param height: height of the changed surface view.
+        void OnDisplayGeometryChanged(int display_rotation, int width, int height);
+
+        void OnObjectRotationChanged(int rotation);
+
+        void OnAction(float value);
+
+        // OnDrawFrame is called on the OpenGL thread to render the next frame.
+        void OnDrawFrame();
+
+        bool OnTouchedFirst(float x, float y, int drawMode);
+
+        void OnTouchTranslate(float x, float y);
+
+        void OnEditTouched(float x, float y);
+
+        void OnTouchedFinal(int type);
+
+        void RemoveAnchor(ArAnchor* anchor);
+
+        void AddAnchor(ArAnchor* anchor, ArPlane* containingPlane);
+
+        void UpdateMatrixMaps(ArAnchor* anchorKey, glm::mat4 aaMat, glm::mat4 caMat, glm::mat4 snapMat);
+
+        void SetModelMatrices(glm::mat4& aaMat, glm::mat4& caMat, glm::mat4& snapMat, const glm::mat4& planeModel);
+
+        void SetCameraAlignedMatrix(glm::mat4& caMat, glm::vec3 hitPos, glm::mat4& planeModel, const glm::mat4& initRotation);
+
+        // Returns true if any planes have been detected.  Used for hiding the
+        // "searching for planes" snackbar.
+        bool HasDetectedPlanes() const { return plane_count_ > 0; }
+
+        glm::mat4
+        ComputeCameraAlignedMatrix(ArPlane *arPlane, glm::mat4 planeModel, glm::mat4 initRotation,
+                                   glm::vec4 anchorPos,
+                                   glm::vec3 cameraPos, glm::vec3 hitPos,
+                                   float cameraDisplayOutRaw[]);
+
+    private:
+        ArSession *ar_session_ = nullptr;
+        ArFrame *ar_frame_ = nullptr;
+
+        PendingAnchor* pendingAnchor = nullptr;
+
+        //SKIA VARS
+        sk_sp<GrContext> grContext;
+        sk_sp<skottie::Animation> fAnim;
+        SkScalar fAnimT = 0;
+
+        bool install_requested_ = false;
+        int width_ = 1;
+        int height_ = 1;
+        int display_rotation_ = 0;
+
+        int currentObjectRotation = 0;
+        float currentValue = 0;
+
+        std::vector<glm::vec3> begins;
+        std::vector<glm::vec3> ends;
+
+        AAssetManager *const asset_manager_;
+
+        // The anchors at which we are drawing android models
+        std::vector<ArAnchor *> tracked_obj_set_;
+
+        // Stores the randomly-selected color each plane is drawn with
+        std::unordered_map<ArPlane *, glm::vec3> plane_color_map_;
+
+        std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_axis_aligned_map_;
+        std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_camera_aligned_map_;
+        std::unordered_map<ArAnchor *, SkMatrix44> anchor_skmat4_snap_aligned_map_;
+
+        std::unordered_map<ArPlane *, std::vector<ArAnchor*>> plane_anchors_map_;
+        std::unordered_map<ArAnchor *, ArPlane*> anchor_plane_map_;
+
+        // The first plane is always rendered in white, if this is true then a plane
+        // at some point has been found.
+        bool first_plane_has_been_found_ = false;
+
+        PointCloudRenderer point_cloud_renderer_;
+        BackgroundRenderer background_renderer_;
+        PlaneRenderer plane_renderer_;
+
+        int32_t plane_count_ = 0;
+    };
+}  // namespace hello_ar
+
+#endif  // C_ARCORE_HELLOE_AR_HELLO_AR_APPLICATION_H_
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc
new file mode 100644
index 0000000..01cbff2
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.cc
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 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 <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <jni.h>
+
+#include "hello_ar_application.h"
+
+#define JNI_METHOD(return_type, method_name) \
+  JNIEXPORT return_type JNICALL              \
+      Java_org_skia_arcore_JniInterface_##method_name
+
+extern "C" {
+
+namespace {
+// maintain a reference to the JVM so we can use it later.
+    static JavaVM *g_vm = nullptr;
+
+    inline jlong jptr(hello_ar::HelloArApplication *native_hello_ar_application) {
+        return reinterpret_cast<intptr_t>(native_hello_ar_application);
+    }
+
+    inline hello_ar::HelloArApplication *native(jlong ptr) {
+        return reinterpret_cast<hello_ar::HelloArApplication *>(ptr);
+    }
+
+}  // namespace
+
+jint JNI_OnLoad(JavaVM *vm, void *) {
+    g_vm = vm;
+    return JNI_VERSION_1_6;
+}
+
+JNI_METHOD(jlong, createNativeApplication)
+(JNIEnv *env, jclass, jobject j_asset_manager) {
+    AAssetManager *asset_manager = AAssetManager_fromJava(env, j_asset_manager);
+    return jptr(new hello_ar::HelloArApplication(asset_manager));
+}
+
+JNI_METHOD(void, destroyNativeApplication)
+(JNIEnv *, jclass, jlong native_application) {
+    delete native(native_application);
+}
+
+JNI_METHOD(void, onPause)
+(JNIEnv *, jclass, jlong native_application) {
+    native(native_application)->OnPause();
+}
+
+JNI_METHOD(void, onResume)
+(JNIEnv *env, jclass, jlong native_application, jobject context,
+ jobject activity) {
+    native(native_application)->OnResume(env, context, activity);
+}
+
+JNI_METHOD(void, onGlSurfaceCreated)
+(JNIEnv *, jclass, jlong native_application) {
+    native(native_application)->OnSurfaceCreated();
+}
+
+JNI_METHOD(void, onDisplayGeometryChanged)
+(JNIEnv *, jobject, jlong native_application, int display_rotation, int width,
+ int height) {
+    native(native_application)
+            ->OnDisplayGeometryChanged(display_rotation, width, height);
+}
+
+JNI_METHOD(void, onObjectRotationChanged)
+(JNIEnv *, jobject, jlong native_application, int rotation) {
+    native(native_application)
+            ->OnObjectRotationChanged(rotation);
+}
+
+JNI_METHOD(void, onAction)
+(JNIEnv *, jobject, jlong native_application, jfloat value) {
+    native(native_application)->OnAction(value);
+}
+
+JNI_METHOD(void, onGlSurfaceDrawFrame)
+(JNIEnv *, jclass, jlong native_application) {
+    native(native_application)->OnDrawFrame();
+}
+
+JNI_METHOD(void, onTouchTranslate)
+(JNIEnv *, jclass, jlong native_application, jfloat x, jfloat y) {
+    return native(native_application)->OnTouchTranslate(x, y);
+}
+
+JNI_METHOD(bool, onTouchedFirst)
+(JNIEnv *, jclass, jlong native_application, jfloat x, jfloat y, int drawMode) {
+    return native(native_application)->OnTouchedFirst(x, y, drawMode);
+}
+
+JNI_METHOD(void, onTouchedFinal)
+(JNIEnv *, jclass, jlong native_application, int type) {
+    native(native_application)->OnTouchedFinal(type);
+}
+
+JNI_METHOD(jboolean, hasDetectedPlanes)
+(JNIEnv *, jclass, jlong native_application) {
+    return static_cast<jboolean>(
+            native(native_application)->HasDetectedPlanes() ? JNI_TRUE : JNI_FALSE);
+}
+
+JNIEnv *GetJniEnv() {
+    JNIEnv *env;
+    jint result = g_vm->AttachCurrentThread(&env, nullptr);
+    return result == JNI_OK ? env : nullptr;
+}
+
+jclass FindClass(const char *classname) {
+    JNIEnv *env = GetJniEnv();
+    return env->FindClass(classname);
+}
+
+}  // extern "C"
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h
new file mode 100644
index 0000000..fe19cfc
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/jni_interface.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLOE_AR_JNI_INTERFACE_H_
+#define C_ARCORE_HELLOE_AR_JNI_INTERFACE_H_
+
+#include <jni.h>
+/**
+ * Helper functions to provide access to Java from C via JNI.
+ */
+extern "C" {
+
+// Helper function used to access the jni environment on the current thread.
+// In this sample, no consideration is made for detaching the thread when the
+// thread exits. This can cause memory leaks, so production applications should
+// detach when the thread no longer needs access to the JVM.
+JNIEnv *GetJniEnv();
+
+jclass FindClass(const char *classname);
+}  // extern "C"
+#endif
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc
new file mode 100644
index 0000000..f6a9c67
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.cc
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hello_ar_application.h"
+#include "plane_renderer.h"
+#include "util.h"
+#include "SkCanvas.h"
+
+namespace hello_ar {
+    PendingAnchor::PendingAnchor(SkPoint touchLocation) : touchLocation(touchLocation) {}
+
+    PendingAnchor::~PendingAnchor() {}
+
+    SkPoint PendingAnchor::GetTouchLocation() {
+        return touchLocation;
+    }
+
+    bool PendingAnchor::GetEditMode() {
+        return editMode;
+    }
+
+    ArPlane* PendingAnchor::GetContainingPlane() {
+        return containingPlane;
+    }
+
+    glm::vec4 PendingAnchor::GetAnchorPos(ArSession* arSession) {
+        float poseRaw[] = {0, 0, 0, 0, 0, 0, 0};
+        ArPose* anchorPose = nullptr;
+        ArPose_create(arSession, poseRaw, &anchorPose);
+        ArAnchor_getPose(arSession, this->anchor, anchorPose);
+        ArPose_getPoseRaw(arSession, anchorPose, poseRaw);
+        ArPose_destroy(anchorPose);
+        glm::vec4 anchorPos = glm::vec4(poseRaw[4], poseRaw[5], poseRaw[6], 1);
+        return anchorPos;
+    }
+
+    ArAnchor* PendingAnchor::GetArAnchor() {
+        return anchor;
+    }
+
+    void PendingAnchor::SetArAnchor(ArAnchor* anchor) {
+        this->anchor = anchor;
+    }
+
+    void PendingAnchor::SetEditMode(bool editMode) {
+        this->editMode = editMode;
+    }
+
+    void PendingAnchor::SetContainingPlane(ArPlane* plane) {
+        this->containingPlane = plane;
+    }
+
+
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h
new file mode 100644
index 0000000..2694b67
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/pending_anchor.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLO_AR_PENDING_ANCHOR_H_
+#define C_ARCORE_HELLO_AR_PENDING_ANCHOR_H_
+
+#include <gl/GrGLTypes.h>
+#include <GrBackendSurface.h>
+
+#include "arcore_c_api.h"
+#include "glm.h"
+
+namespace hello_ar {
+    class PendingAnchor {
+    public:
+        PendingAnchor(SkPoint touchLocation);
+        ~PendingAnchor();
+
+        SkPoint GetTouchLocation();
+        bool GetEditMode();
+        ArPlane* GetContainingPlane();
+        glm::vec4 GetAnchorPos(ArSession* arSession);
+        ArAnchor* GetArAnchor();
+
+        void SetArAnchor(ArAnchor* anchor);
+        void SetEditMode(bool editMode);
+        void SetContainingPlane(ArPlane* plane);
+
+    private:
+        SkPoint touchLocation;
+        bool editMode = false;
+        ArAnchor* anchor;
+        ArPlane* containingPlane;
+    };
+}  // namespace hello_ar
+
+#endif
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc
new file mode 100644
index 0000000..50d8f8b
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.cc
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2017 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 "plane_renderer.h"
+#include "util.h"
+
+namespace hello_ar {
+    namespace {
+        constexpr char kVertexShader[] = R"(
+    precision highp float;
+    precision highp int;
+    attribute vec3 vertex;
+    varying vec2 v_textureCoords;
+    varying float v_alpha;
+
+    uniform mat4 mvp;
+    uniform mat4 model_mat;
+    uniform vec3 normal;
+
+    void main() {
+      // Vertex Z value is used as the alpha in this shader.
+      v_alpha = vertex.z;
+
+      vec4 local_pos = vec4(vertex.x, 0.0, vertex.y, 1.0);
+      gl_Position = mvp * local_pos;
+      vec4 world_pos = model_mat * local_pos;
+
+      // Construct two vectors that are orthogonal to the normal.
+      // This arbitrary choice is not co-linear with either horizontal
+      // or vertical plane normals.
+      const vec3 arbitrary = vec3(1.0, 1.0, 0.0);
+      vec3 vec_u = normalize(cross(normal, arbitrary));
+      vec3 vec_v = normalize(cross(normal, vec_u));
+
+      // Project vertices in world frame onto vec_u and vec_v.
+      v_textureCoords = vec2(
+         dot(world_pos.xyz, vec_u), dot(world_pos.xyz, vec_v));
+    })";
+
+        constexpr char kFragmentShader[] = R"(
+    precision highp float;
+    precision highp int;
+    uniform sampler2D texture;
+    uniform vec3 color;
+    varying vec2 v_textureCoords;
+    varying float v_alpha;
+    void main() {
+      float r = texture2D(texture, v_textureCoords).r;
+      gl_FragColor = vec4(color.xyz, r * v_alpha);
+    })";
+    }  // namespace
+
+    void PlaneRenderer::InitializeGlContent(AAssetManager *asset_manager) {
+        shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader);
+
+        if (!shader_program_) {
+            LOGE("Could not create program.");
+        }
+
+        uniform_mvp_mat_ = glGetUniformLocation(shader_program_, "mvp");
+        uniform_texture_ = glGetUniformLocation(shader_program_, "texture");
+        uniform_model_mat_ = glGetUniformLocation(shader_program_, "model_mat");
+        uniform_normal_vec_ = glGetUniformLocation(shader_program_, "normal");
+        uniform_color_ = glGetUniformLocation(shader_program_, "color");
+        attri_vertices_ = glGetAttribLocation(shader_program_, "vertex");
+
+        glGenTextures(1, &texture_id_);
+        glBindTexture(GL_TEXTURE_2D, texture_id_);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+                        GL_LINEAR_MIPMAP_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        if (!util::LoadPngFromAssetManager(GL_TEXTURE_2D, "models/trigrid.png")) {
+            LOGE("Could not load png texture for planes.");
+        }
+
+        glGenerateMipmap(GL_TEXTURE_2D);
+
+        glBindTexture(GL_TEXTURE_2D, 0);
+
+        util::CheckGlError("plane_renderer::InitializeGlContent()");
+    }
+
+    void PlaneRenderer::Draw(const glm::mat4 &projection_mat,
+                             const glm::mat4 &view_mat, const ArSession *ar_session,
+                             const ArPlane *ar_plane, const glm::vec3 &color) {
+        if (!shader_program_) {
+            LOGE("shader_program is null.");
+            return;
+        }
+
+        UpdateForPlane(ar_session, ar_plane);
+
+        glUseProgram(shader_program_);
+        glDepthMask(GL_FALSE);
+
+        glActiveTexture(GL_TEXTURE0);
+        glUniform1i(uniform_texture_, 0);
+        glBindTexture(GL_TEXTURE_2D, texture_id_);
+
+        // Compose final mvp matrix for this plane renderer.
+        glUniformMatrix4fv(uniform_mvp_mat_, 1, GL_FALSE,
+                           glm::value_ptr(projection_mat * view_mat * model_mat_));
+
+        glUniformMatrix4fv(uniform_model_mat_, 1, GL_FALSE,
+                           glm::value_ptr(model_mat_));
+        glUniform3f(uniform_normal_vec_, normal_vec_.x, normal_vec_.y, normal_vec_.z);
+        glUniform3f(uniform_color_, color.x, color.y, color.z);
+
+        glEnableVertexAttribArray(attri_vertices_);
+        glVertexAttribPointer(attri_vertices_, 3, GL_FLOAT, GL_FALSE, 0,
+                              vertices_.data());
+
+        glDrawElements(GL_TRIANGLES, triangles_.size(), GL_UNSIGNED_SHORT,
+                       triangles_.data());
+
+        glUseProgram(0);
+        glDepthMask(GL_TRUE);
+        util::CheckGlError("plane_renderer::Draw()");
+    }
+
+    void PlaneRenderer::UpdateForPlane(const ArSession *ar_session,
+                                       const ArPlane *ar_plane) {
+        // The following code generates a triangle mesh filling a convex polygon,
+        // including a feathered edge for blending.
+        //
+        // The indices shown in the diagram are used in comments below.
+        // _______________     0_______________1
+        // |             |      |4___________5|
+        // |             |      | |         | |
+        // |             | =>   | |         | |
+        // |             |      | |         | |
+        // |             |      |7-----------6|
+        // ---------------     3---------------2
+
+        vertices_.clear();
+        triangles_.clear();
+
+        int32_t polygon_length;
+        ArPlane_getPolygonSize(ar_session, ar_plane, &polygon_length);
+
+        if (polygon_length == 0) {
+            LOGE("PlaneRenderer::UpdatePlane, no valid plane polygon is found");
+            return;
+        }
+
+        const int32_t vertices_size = polygon_length / 2;
+        std::vector<glm::vec2> raw_vertices(vertices_size);
+        ArPlane_getPolygon(ar_session, ar_plane,
+                           glm::value_ptr(raw_vertices.front()));
+
+        // Fill vertex 0 to 3. Note that the vertex.xy are used for x and z
+        // position. vertex.z is used for alpha. The outter polygon's alpha
+        // is 0.
+        for (int32_t i = 0; i < vertices_size; ++i) {
+            vertices_.push_back(glm::vec3(raw_vertices[i].x, raw_vertices[i].y, 0.0f));
+        }
+
+        util::ScopedArPose scopedArPose(ar_session);
+        ArPlane_getCenterPose(ar_session, ar_plane, scopedArPose.GetArPose());
+        ArPose_getMatrix(ar_session, scopedArPose.GetArPose(),
+                         glm::value_ptr(model_mat_));
+        normal_vec_ = util::GetPlaneNormal(ar_session, *scopedArPose.GetArPose());
+
+        // Feather distance 0.2 meters.
+        const float kFeatherLength = 0.2f;
+        // Feather scale over the distance between plane center and vertices.
+        const float kFeatherScale = 0.2f;
+
+        // Fill vertex 4 to 7, with alpha set to 1.
+        for (int32_t i = 0; i < vertices_size; ++i) {
+            // Vector from plane center to current point.
+            glm::vec2 v = raw_vertices[i];
+            const float scale =
+                    1.0f - std::min((kFeatherLength / glm::length(v)), kFeatherScale);
+            const glm::vec2 result_v = scale * v;
+
+            vertices_.push_back(glm::vec3(result_v.x, result_v.y, 1.0f));
+        }
+
+        const int32_t vertices_length = vertices_.size();
+        const int32_t half_vertices_length = vertices_length / 2;
+
+        // Generate triangle (4, 5, 6) and (4, 6, 7).
+        for (int i = half_vertices_length + 1; i < vertices_length - 1; ++i) {
+            triangles_.push_back(half_vertices_length);
+            triangles_.push_back(i);
+            triangles_.push_back(i + 1);
+        }
+
+        // Generate triangle (0, 1, 4), (4, 1, 5), (5, 1, 2), (5, 2, 6),
+        // (6, 2, 3), (6, 3, 7), (7, 3, 0), (7, 0, 4)
+        for (int i = 0; i < half_vertices_length; ++i) {
+            triangles_.push_back(i);
+            triangles_.push_back((i + 1) % half_vertices_length);
+            triangles_.push_back(i + half_vertices_length);
+
+            triangles_.push_back(i + half_vertices_length);
+            triangles_.push_back((i + 1) % half_vertices_length);
+            triangles_.push_back((i + half_vertices_length + 1) % half_vertices_length +
+                                 half_vertices_length);
+        }
+    }
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h
new file mode 100644
index 0000000..c1f75f2
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/plane_renderer.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_
+#define C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <android/asset_manager.h>
+#include <array>
+#include <cstdint>
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#include "arcore_c_api.h"
+#include "glm.h"
+
+namespace hello_ar {
+
+// PlaneRenderer renders ARCore plane type.
+    class PlaneRenderer {
+    public:
+        PlaneRenderer() = default;
+
+        ~PlaneRenderer() = default;
+
+        // Sets up OpenGL state used by the plane renderer.  Must be called on the
+        // OpenGL thread.
+        void InitializeGlContent(AAssetManager *asset_manager);
+
+        // Draws the provided plane.
+        void Draw(const glm::mat4 &projection_mat, const glm::mat4 &view_mat,
+                  const ArSession *ar_session, const ArPlane *ar_plane,
+                  const glm::vec3 &color);
+
+    private:
+        void UpdateForPlane(const ArSession *ar_session, const ArPlane *ar_plane);
+
+        std::vector<glm::vec3> vertices_;
+        std::vector<GLushort> triangles_;
+        glm::mat4 model_mat_ = glm::mat4(1.0f);
+        glm::vec3 normal_vec_ = glm::vec3(0.0f);
+
+        GLuint texture_id_;
+
+        GLuint shader_program_;
+        GLint attri_vertices_;
+        GLint uniform_mvp_mat_;
+        GLint uniform_texture_;
+        GLint uniform_model_mat_;
+        GLint uniform_normal_vec_;
+        GLint uniform_color_;
+    };
+}  // namespace hello_ar
+
+#endif  // C_ARCORE_HELLOE_AR_PLANE_RENDERER_H_
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc
new file mode 100644
index 0000000..c9ab693
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 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 "point_cloud_renderer.h"
+#include "util.h"
+
+namespace hello_ar {
+    namespace {
+        constexpr char kVertexShader[] = R"(
+    attribute vec4 vertex;
+    uniform mat4 mvp;
+    void main() {
+      gl_PointSize = 5.0;
+      // Pointcloud vertex's w component is confidence value.
+      // Not used in renderer.
+      gl_Position = mvp * vec4(vertex.xyz, 1.0);
+    })";
+
+        constexpr char kFragmentShader[] = R"(
+    precision lowp float;
+    void main() {
+      gl_FragColor = vec4(0.1215, 0.7372, 0.8235, 1.0);
+    })";
+    }  // namespace
+
+    void PointCloudRenderer::InitializeGlContent() {
+        shader_program_ = util::CreateProgram(kVertexShader, kFragmentShader);
+
+        CHECK(shader_program_);
+
+        attribute_vertices_ = glGetAttribLocation(shader_program_, "vertex");
+        uniform_mvp_mat_ = glGetUniformLocation(shader_program_, "mvp");
+
+        util::CheckGlError("point_cloud_renderer::InitializeGlContent()");
+    }
+
+    void PointCloudRenderer::Draw(glm::mat4 mvp_matrix, ArSession *ar_session,
+                                  ArPointCloud *ar_point_cloud) const {
+        CHECK(shader_program_);
+
+        glUseProgram(shader_program_);
+
+        int32_t number_of_points = 0;
+        ArPointCloud_getNumberOfPoints(ar_session, ar_point_cloud, &number_of_points);
+        if (number_of_points <= 0) {
+            return;
+        }
+
+        const float *point_cloud_data;
+        ArPointCloud_getData(ar_session, ar_point_cloud, &point_cloud_data);
+
+        glUniformMatrix4fv(uniform_mvp_mat_, 1, GL_FALSE, glm::value_ptr(mvp_matrix));
+
+        glEnableVertexAttribArray(attribute_vertices_);
+        glVertexAttribPointer(attribute_vertices_, 4, GL_FLOAT, GL_FALSE, 0,
+                              point_cloud_data);
+
+        glDrawArrays(GL_POINTS, 0, number_of_points);
+
+        glUseProgram(0);
+        util::CheckGlError("PointCloudRenderer::Draw");
+    }
+
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h
new file mode 100644
index 0000000..95f70e5
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/point_cloud_renderer.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_
+#define C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <cstdlib>
+#include <vector>
+
+#include "arcore_c_api.h"
+#include "glm.h"
+
+namespace hello_ar {
+
+    class PointCloudRenderer {
+    public:
+        // Default constructor of PointCloudRenderer.
+        PointCloudRenderer() = default;
+
+        // Default deconstructor of PointCloudRenderer.
+        ~PointCloudRenderer() = default;
+
+        // Initialize the GL content, needs to be called on GL thread.
+        void InitializeGlContent();
+
+        // Render the AR point cloud.
+        //
+        // @param mvp_matrix, the model view projection matrix of point cloud.
+        // @param ar_session, the session that is used to query point cloud points
+        //     from ar_point_cloud.
+        // @param ar_point_cloud, point cloud data to for rendering.
+        void Draw(glm::mat4 mvp_matrix, ArSession *ar_session,
+                  ArPointCloud *ar_point_cloud) const;
+
+    private:
+        GLuint shader_program_;
+        GLint attribute_vertices_;
+        GLint uniform_mvp_mat_;
+    };
+}  // namespace hello_ar
+
+#endif  // C_ARCORE_HELLOE_AR_POINT_CLOUD_RENDERER_H_
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/util.cc b/platform_tools/android/apps/arcore/src/main/cpp/util.cc
new file mode 100644
index 0000000..d18719f
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/util.cc
@@ -0,0 +1,329 @@
+/*
+ * Copyright 2017 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 "util.h"
+
+#include <unistd.h>
+#include <sstream>
+#include <string>
+#include <SkMatrix44.h>
+#include <gtx/string_cast.inl>
+
+#include "jni_interface.h"
+
+namespace hello_ar {
+    namespace util {
+
+        void CheckGlError(const char *operation) {
+            bool anyError = false;
+            for (GLint error = glGetError(); error; error = glGetError()) {
+                LOGE("after %s() glError (0x%x)\n", operation, error);
+                anyError = true;
+            }
+            if (anyError) {
+                abort();
+            }
+        }
+
+        // Convenience function used in CreateProgram below.
+        static GLuint LoadShader(GLenum shader_type, const char *shader_source) {
+            GLuint shader = glCreateShader(shader_type);
+            if (!shader) {
+                return shader;
+            }
+
+            glShaderSource(shader, 1, &shader_source, nullptr);
+            glCompileShader(shader);
+            GLint compiled = 0;
+            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+
+            if (!compiled) {
+                GLint info_len = 0;
+
+                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_len);
+                if (!info_len) {
+                    return shader;
+                }
+
+                char *buf = reinterpret_cast<char *>(malloc(info_len));
+                if (!buf) {
+                    return shader;
+                }
+
+                glGetShaderInfoLog(shader, info_len, nullptr, buf);
+                LOGE("hello_ar::util::Could not compile shader %d:\n%s\n", shader_type,
+                     buf);
+                free(buf);
+                glDeleteShader(shader);
+                shader = 0;
+            }
+
+            return shader;
+        }
+
+        GLuint CreateProgram(const char *vertex_source, const char *fragment_source) {
+            GLuint vertexShader = LoadShader(GL_VERTEX_SHADER, vertex_source);
+            if (!vertexShader) {
+                return 0;
+            }
+
+            GLuint fragment_shader = LoadShader(GL_FRAGMENT_SHADER, fragment_source);
+            if (!fragment_shader) {
+                return 0;
+            }
+
+            GLuint program = glCreateProgram();
+            if (program) {
+                glAttachShader(program, vertexShader);
+                CheckGlError("hello_ar::util::glAttachShader");
+                glAttachShader(program, fragment_shader);
+                CheckGlError("hello_ar::util::glAttachShader");
+                glLinkProgram(program);
+                GLint link_status = GL_FALSE;
+                glGetProgramiv(program, GL_LINK_STATUS, &link_status);
+                if (link_status != GL_TRUE) {
+                    GLint buf_length = 0;
+                    glGetProgramiv(program, GL_INFO_LOG_LENGTH, &buf_length);
+                    if (buf_length) {
+                        char *buf = reinterpret_cast<char *>(malloc(buf_length));
+                        if (buf) {
+                            glGetProgramInfoLog(program, buf_length, nullptr, buf);
+                            LOGE("hello_ar::util::Could not link program:\n%s\n", buf);
+                            free(buf);
+                        }
+                    }
+                    glDeleteProgram(program);
+                    program = 0;
+                }
+            }
+            return program;
+        }
+
+        bool LoadPngFromAssetManager(int target, const std::string &path) {
+            JNIEnv *env = GetJniEnv();
+
+            // Put all the JNI values in a structure that is statically initalized on the
+            // first call to this method.  This makes it thread safe in the unlikely case
+            // of multiple threads calling this method.
+            static struct JNIData {
+                jclass helper_class;
+                jmethodID load_image_method;
+                jmethodID load_texture_method;
+            } jniIds = [env]() -> JNIData {
+                constexpr char kHelperClassName[] =
+                        "org/skia/arcore/JniInterface";
+                constexpr char kLoadImageMethodName[] = "loadImage";
+                constexpr char kLoadImageMethodSignature[] =
+                        "(Ljava/lang/String;)Landroid/graphics/Bitmap;";
+                constexpr char kLoadTextureMethodName[] = "loadTexture";
+                constexpr char kLoadTextureMethodSignature[] =
+                        "(ILandroid/graphics/Bitmap;)V";
+                jclass helper_class = FindClass(kHelperClassName);
+                if (helper_class) {
+                    helper_class = static_cast<jclass>(env->NewGlobalRef(helper_class));
+                    jmethodID load_image_method = env->GetStaticMethodID(
+                            helper_class, kLoadImageMethodName, kLoadImageMethodSignature);
+                    jmethodID load_texture_method = env->GetStaticMethodID(
+                            helper_class, kLoadTextureMethodName, kLoadTextureMethodSignature);
+                    return {helper_class, load_image_method, load_texture_method};
+                }
+                LOGE("hello_ar::util::Could not find Java helper class %s",
+                     kHelperClassName);
+                return {};
+            }();
+
+            if (!jniIds.helper_class) {
+                return false;
+            }
+
+            jstring j_path = env->NewStringUTF(path.c_str());
+
+            jobject image_obj = env->CallStaticObjectMethod(
+                    jniIds.helper_class, jniIds.load_image_method, j_path);
+
+            if (j_path) {
+                env->DeleteLocalRef(j_path);
+            }
+
+            env->CallStaticVoidMethod(jniIds.helper_class, jniIds.load_texture_method,
+                                      target, image_obj);
+            return true;
+        }
+
+        void GetTransformMatrixFromPose(ArSession *ar_session,
+                                        const ArPose *ar_pose,
+                                        glm::mat4 *out_model_mat) {
+            if (out_model_mat == nullptr) {
+                LOGE("util::GetTransformMatrixFromPose model_mat is null.");
+                return;
+            }
+            ArPose_getMatrix(ar_session, ar_pose,
+                             glm::value_ptr(*out_model_mat));
+        }
+
+        glm::vec3 GetPlaneNormal(const ArSession *ar_session,
+                                 const ArPose &plane_pose) {
+            float plane_pose_raw[7] = {0.f};
+            ArPose_getPoseRaw(ar_session, &plane_pose, plane_pose_raw);
+            glm::quat plane_quaternion(plane_pose_raw[3], plane_pose_raw[0],
+                                       plane_pose_raw[1], plane_pose_raw[2]);
+            // Get normal vector, normal is defined to be positive Y-position in local
+            // frame.
+            return glm::rotate(plane_quaternion, glm::vec3(0., 1.f, 0.));
+        }
+
+        float CalculateDistanceToPlane(const ArSession *ar_session,
+                                       const ArPose &plane_pose,
+                                       const ArPose &camera_pose) {
+            float plane_pose_raw[7] = {0.f};
+            ArPose_getPoseRaw(ar_session, &plane_pose, plane_pose_raw);
+            glm::vec3 plane_position(plane_pose_raw[4], plane_pose_raw[5],
+                                     plane_pose_raw[6]);
+            glm::vec3 normal = GetPlaneNormal(ar_session, plane_pose);
+
+            float camera_pose_raw[7] = {0.f};
+            ArPose_getPoseRaw(ar_session, &camera_pose, camera_pose_raw);
+            glm::vec3 camera_P_plane(camera_pose_raw[4] - plane_position.x,
+                                     camera_pose_raw[5] - plane_position.y,
+                                     camera_pose_raw[6] - plane_position.z);
+            return glm::dot(normal, camera_P_plane);
+        }
+
+        glm::mat4 GetCameraRotationMatrix(float cameraOutRaw[]) {
+            glm::mat4 cameraRotation(1);
+            glm::quat cameraQuat = glm::quat(cameraOutRaw[0], cameraOutRaw[1], cameraOutRaw[2],
+                                             cameraOutRaw[3]);
+            cameraRotation = glm::toMat4(cameraQuat);
+            glm::vec4 temp = cameraRotation[0];
+            cameraRotation[0] = cameraRotation[2];
+            cameraRotation[2] = temp;
+            return cameraRotation;
+        }
+
+        void GetCameraInfo(ArSession* arSession, ArFrame* arFrame, glm::vec3& cameraPos, glm::mat4& cameraRotation) {
+            //Acquire camera
+            ArCamera *ar_camera;
+            ArFrame_acquireCamera(arSession, arFrame, &ar_camera);
+
+            //Get camera pose
+            ArPose *camera_pose = nullptr;
+            ArPose_create(arSession, nullptr, &camera_pose);
+            ArCamera_getDisplayOrientedPose(arSession, ar_camera, camera_pose);
+
+            //Get camera raw info
+            float outCameraRaw[] = {0, 0, 0, 0, 0, 0, 0};
+            ArPose_getPoseRaw(arSession, camera_pose, outCameraRaw);
+            ArPose_destroy(camera_pose);
+
+            //Write to out variables
+            cameraPos = glm::vec3(outCameraRaw[4], outCameraRaw[5], outCameraRaw[6]);
+            cameraRotation = util::GetCameraRotationMatrix(outCameraRaw);
+
+            //Release camera
+            ArCamera_release(ar_camera);
+        }
+
+        SkMatrix44 GlmMatToSkMat(const glm::mat4 m) {
+            SkMatrix44 skMat = SkMatrix44::kIdentity_Constructor;
+            for (int i = 0; i < 4; i++) {
+                for (int j = 0; j < 4; j++) {
+                    skMat.set(j, i, m[i][j]);
+                }
+            }
+            return skMat;
+        }
+
+        glm::mat4 SkMatToGlmMat(const SkMatrix44 m) {
+            glm::mat4 glmMat(1);
+            for (int i = 0; i < 4; i++) {
+                for (int j = 0; j < 4; j++) {
+                    glmMat[i][j] = m.get(j, i);
+                }
+            }
+            return glmMat;
+        }
+
+        void Log4x4Matrix(float raw_matrix[16]) {
+            LOGI(
+                    "%f, %f, %f, %f\n"
+                            "%f, %f, %f, %f\n"
+                            "%f, %f, %f, %f\n"
+                            "%f, %f, %f, %f\n",
+                    raw_matrix[0], raw_matrix[1], raw_matrix[2], raw_matrix[3], raw_matrix[4],
+                    raw_matrix[5], raw_matrix[6], raw_matrix[7], raw_matrix[8], raw_matrix[9],
+                    raw_matrix[10], raw_matrix[11], raw_matrix[12], raw_matrix[13],
+                    raw_matrix[14], raw_matrix[15]);
+        }
+
+        void LogGlmMat(glm::mat4 m, char *type) {
+            std::string str = glm::to_string(m);
+            LOGE("glm Matrix - %s: %s\n", type, str.c_str());
+        }
+
+        void LogSkMat44(SkMatrix44 m, char *type) {
+            LOGE("SkMatrix - %s: [%g, %g, %g, %g] || [%g, %g, %g, %g] || [%g, %g, %g, %g] || [%g, %g, %g, %g] \n",
+                 type,
+                 m.get(0, 0), m.get(1, 0), m.get(2, 0), m.get(3, 0),
+                 m.get(0, 1), m.get(1, 1), m.get(2, 1), m.get(3, 1),
+                 m.get(0, 2), m.get(1, 2), m.get(2, 2), m.get(3, 2),
+                 m.get(0, 3), m.get(1, 3), m.get(2, 3), m.get(3, 3)
+            );
+        }
+
+        void LogSkMat(SkMatrix m, char *type) {
+            LOGE("SkMatrix - %s: [%g, %g, %g] || [%g, %g, %g] || [%g, %g, %g] \n", type,
+                 m.get(0), m.get(3), m.get(6),
+                 m.get(1), m.get(4), m.get(7),
+                 m.get(2), m.get(5), m.get(8)
+            );
+        }
+
+        void LogOrientation(float rotationDirection, float angleRad, char *type) {
+            LOGI("Plane orientation: %s", type);
+            LOGI("Cross dotted with zDir:", rotationDirection);
+            if (rotationDirection == -1) {
+                LOGI("Counter Clockwise %.6f degrees rotation: ", glm::degrees(angleRad));
+            } else {
+                LOGI("Clockwise %.6f degrees rotation: ", glm::degrees(angleRad));
+            }
+        }
+
+        float Dot(glm::vec3 u, glm::vec3 v) {
+            float result = u.x * v.x + u.y * v.y + u.z * v.z;
+            return result;
+        }
+
+        float Magnitude(glm::vec3 u) {
+            float result = u.x * u.x + u.y * u.y + u.z * u.z;
+            return sqrt(result);
+        }
+
+        float AngleRad(glm::vec3 u, glm::vec3 v) {
+            float dot = util::Dot(u, v);
+            float scale = (util::Magnitude(u) * util::Magnitude(v));
+            float cosine = dot / scale;
+            float acosine = acos(cosine);
+            return acosine;
+        }
+
+        glm::vec3 ProjectOntoPlane(glm::vec3 in, glm::vec3 normal) {
+            float dot = util::Dot(in, normal);
+            float multiplier = dot / (util::Magnitude(normal) * util::Magnitude(normal));
+            glm::vec3 out = in - multiplier * normal;
+            return out;
+        }
+
+    }  // namespace util
+}  // namespace hello_ar
diff --git a/platform_tools/android/apps/arcore/src/main/cpp/util.h b/platform_tools/android/apps/arcore/src/main/cpp/util.h
new file mode 100644
index 0000000..88bc2f9
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/cpp/util.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef C_ARCORE_HELLOE_AR_UTIL_H_
+#define C_ARCORE_HELLOE_AR_UTIL_H_
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <android/asset_manager.h>
+#include <android/log.h>
+#include <errno.h>
+#include <jni.h>
+#include <cstdint>
+#include <cstdlib>
+#include <vector>
+#include <SkMatrix44.h>
+
+#include "arcore_c_api.h"
+#include "glm.h"
+
+#ifndef LOGI
+#define LOGI(...) \
+  __android_log_print(ANDROID_LOG_INFO, "hello_ar_example_c", __VA_ARGS__)
+#endif  // LOGI
+
+#ifndef LOGE
+#define LOGE(...) \
+  __android_log_print(ANDROID_LOG_ERROR, "hello_ar_example_c", __VA_ARGS__)
+#endif  // LOGE
+
+#ifndef CHECK
+#define CHECK(condition)                                                   \
+  if (!(condition)) {                                                      \
+    LOGE("*** CHECK FAILED at %s:%d: %s", __FILE__, __LINE__, #condition); \
+    abort();                                                               \
+  }
+#endif  // CHECK
+
+namespace hello_ar {
+    // Utilities
+    namespace util {
+
+        // Provides a scoped allocated instance of Anchor.
+        // Can be treated as an ArAnchor*.
+        class ScopedArPose {
+        public:
+            explicit ScopedArPose(const ArSession *session) {
+                ArPose_create(session, nullptr, &pose_);
+            }
+
+            ~ScopedArPose() { ArPose_destroy(pose_); }
+
+            ArPose *GetArPose() { return pose_; }
+
+            // Delete copy constructors.
+            ScopedArPose(const ScopedArPose &) = delete;
+
+            void operator=(const ScopedArPose &) = delete;
+
+        private:
+            ArPose *pose_;
+        };
+
+        /* GL Utils */
+        // Check GL error, and abort if an error is encountered.
+        //
+        // @param operation, the name of the GL function call.
+        void CheckGlError(const char *operation);
+
+        // Create a shader program ID.
+        //
+        // @param vertex_source, the vertex shader source.
+        // @param fragment_source, the fragment shader source.
+        // @return
+        GLuint CreateProgram(const char *vertex_source, const char *fragment_source);
+
+        // Load png file from assets folder and then assign it to the OpenGL target.
+        // This method must be called from the renderer thread since it will result in
+        // OpenGL calls to assign the image to the texture target.
+        //
+        // @param target, openGL texture target to load the image into.
+        // @param path, path to the file, relative to the assets folder.
+        // @return true if png is loaded correctly, otherwise false.
+        bool LoadPngFromAssetManager(int target, const std::string &path);
+
+
+        /* ARCore utils */
+        void GetTransformMatrixFromPose(ArSession *ar_session, const ArPose *ar_pose, glm::mat4 *out_model_mat);
+
+        // Get the plane's normal from center pose.
+        glm::vec3 GetPlaneNormal(const ArSession *ar_session, const ArPose &plane_pose);
+
+        // Calculate the normal distance to plane from cameraPose, the given planePose
+        // should have y axis parallel to plane's normal, for example plane's center
+        // pose or hit test pose.
+        float CalculateDistanceToPlane(const ArSession *ar_session, const ArPose &plane_pose, const ArPose &camera_pose);
+
+        // Outputs the camera rotation using display orientation
+        glm::mat4 GetCameraRotationMatrix(float cameraOutRaw[]);
+
+        // Computes camera position and orientation (using GetCameraRotationMatrix)
+        void GetCameraInfo(ArSession* arSession, ArFrame* arFrame, glm::vec3& cameraPos, glm::mat4& cameraRotation);
+
+        /* Matrix conversion */
+        SkMatrix44 GlmMatToSkMat(const glm::mat4 m);
+        glm::mat4 SkMatToGlmMat(const SkMatrix44 m);
+
+        /* Logging utils */
+        //Row major output
+        void Log4x4Matrix(float raw_matrix[16]);
+
+        //Column major output
+        void LogGlmMat(glm::mat4 m, char *type);
+        void LogSkMat44(SkMatrix44 m, char *type);
+        void LogSkMat(SkMatrix m, char *type);
+        void LogOrientation(float rotationDirection, float angleRad, char *type);
+
+        /* Vector ops */
+        float Dot(glm::vec3 u, glm::vec3 v);
+        float Magnitude(glm::vec3 u);
+        float AngleRad(glm::vec3 u, glm::vec3 v);
+        glm::vec3 ProjectOntoPlane(glm::vec3 in, glm::vec3 normal);
+    }  // namespace util
+}  // namespace hello_ar
+
+#endif  // C_ARCORE_HELLOE_AR_UTIL_H_
diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java
new file mode 100644
index 0000000..3499ec0
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/CameraPermissionHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 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 org.skia.arcore;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.provider.Settings;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+
+/**
+ * Helper to ask camera permission.
+ */
+public class CameraPermissionHelper {
+    private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA;
+    private static final int CAMERA_PERMISSION_CODE = 0;
+
+    /**
+     * Check to see we have the necessary permissions for this app.
+     */
+    public static boolean hasCameraPermission(Activity activity) {
+        return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    /**
+     * Check to see we have the necessary permissions for this app, and ask for them if we don't.
+     */
+    public static void requestCameraPermission(Activity activity) {
+        ActivityCompat.requestPermissions(
+                activity, new String[]{CAMERA_PERMISSION}, CAMERA_PERMISSION_CODE);
+    }
+
+    /**
+     * Check to see if we need to show the rationale for this permission.
+     */
+    public static boolean shouldShowRequestPermissionRationale(Activity activity) {
+        return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION);
+    }
+
+    /**
+     * Launch Application Setting to grant permission.
+     */
+    public static void launchPermissionSettings(Activity activity) {
+        Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(Uri.fromParts("package", activity.getPackageName(), null));
+        activity.startActivity(intent);
+    }
+}
diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java
new file mode 100644
index 0000000..73651f5
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/HelloArActivity.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2017 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 org.skia.arcore;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.ContextMenu;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.EditText;
+import android.widget.PopupMenu;
+import android.widget.Toast;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * This is a simple example that shows how to create an augmented reality (AR) application using the
+ * ARCore C API.
+ */
+public class HelloArActivity extends AppCompatActivity
+        implements GLSurfaceView.Renderer, DisplayManager.DisplayListener {
+    private static final String TAG = HelloArActivity.class.getSimpleName();
+    private static final int SNACKBAR_UPDATE_INTERVAL_MILLIS = 1000; // In milliseconds.
+
+    private GLSurfaceView mSurfaceView;
+    private Activity activity = null;
+    private boolean mViewportChanged = false;
+    private int mViewportWidth;
+    private int mViewportHeight;
+    private View contextView = null;
+    private int mCurrentObjectRotation = 0;
+    private float mCurrentValue = 0;
+    private float X = 0;
+    private float Y = 0;
+
+    private boolean toEdit = false;
+
+    // Opaque native pointer to the native application instance.
+    private long mNativeApplication;
+    private GestureDetector mGestureDetector;
+
+    private Snackbar mLoadingMessageSnackbar;
+    private Handler mPlaneStatusCheckingHandler;
+    private final Runnable mPlaneStatusCheckingRunnable =
+            new Runnable() {
+                @Override
+                public void run() {
+                    // The runnable is executed on main UI thread.
+                    try {
+                        if (JniInterface.hasDetectedPlanes(mNativeApplication)) {
+                            if (mLoadingMessageSnackbar != null) {
+                                mLoadingMessageSnackbar.dismiss();
+                            }
+                            mLoadingMessageSnackbar = null;
+                        } else {
+                            mPlaneStatusCheckingHandler.postDelayed(
+                                    mPlaneStatusCheckingRunnable, SNACKBAR_UPDATE_INTERVAL_MILLIS);
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, e.getMessage());
+                    }
+                }
+            };
+    private int mDrawMode = -1;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
+        setSupportActionBar(myToolbar);
+
+        activity = this;
+
+        //hide notifications bar
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+                WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+        mSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceview);
+
+        mGestureDetector =
+                new GestureDetector(
+                        this,
+                        new GestureDetector.SimpleOnGestureListener() {
+                            @Override
+                            public boolean onSingleTapUp(final MotionEvent e) {
+                                toEdit = JniInterface.onTouchedFirst(mNativeApplication, e.getX(), e.getY(), mDrawMode);
+
+                                Log.i(TAG, "toEdit: " + toEdit);
+                                X = e.getX();
+                                Y = e.getY();
+                                contextView.showContextMenu(e.getX(), e.getY());
+                                return true;
+                            }
+
+                            @Override
+                            public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+                                Log.i(TAG, "Scrolling!");
+                                JniInterface.onTouchTranslate(mNativeApplication, e2.getX(), e2.getY());
+                                return true;
+                            }
+
+                            @Override
+                            public boolean onDown(MotionEvent e) {
+                                return true;
+                            }
+                        });
+
+        mSurfaceView.setOnTouchListener(
+                new View.OnTouchListener() {
+                    @Override
+                    public boolean onTouch(View v, MotionEvent event) {
+                        return mGestureDetector.onTouchEvent(event);
+                    }
+                });
+
+        // Set up renderer.
+        mSurfaceView.setPreserveEGLContextOnPause(true);
+        mSurfaceView.setEGLContextClientVersion(2);
+        mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
+        mSurfaceView.setRenderer(this);
+        mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+
+        JniInterface.assetManager = getAssets();
+        mNativeApplication = JniInterface.createNativeApplication(getAssets());
+
+        mPlaneStatusCheckingHandler = new Handler();
+
+        //Floating context menu
+        contextView = findViewById(R.id.menuView);
+        this.registerForContextMenu(contextView);
+        View.OnLongClickListener listener = new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                activity.closeContextMenu();
+                return false;
+            }
+        };
+        contextView.setOnLongClickListener(listener);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo)
+    {
+        super.onCreateContextMenu(menu, v, menuInfo);
+
+        if (!toEdit) {
+            MenuInflater inflater = getMenuInflater();
+            inflater.inflate(R.menu.draw_menu, menu);
+            menu.setHeaderTitle("Draw Options");
+        }
+
+        v.setClickable(false);
+        v.setFocusable(false);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
+        switch (item.getItemId()) {
+            case R.id.draw_text:
+                JniInterface.onTouchedFinal(mNativeApplication, 0);
+                return true;
+            case R.id.draw_circle:
+                JniInterface.onTouchedFinal(mNativeApplication, 1);
+                return true;
+            case R.id.draw_rect:
+                JniInterface.onTouchedFinal(mNativeApplication, 2);
+                return true;
+            case R.id.edit_size:
+                return true;
+            case R.id.edit_text:
+                return true;
+            default:
+                return super.onContextItemSelected(item);
+        }
+    }
+
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.rotation_mode, menu);
+        return true;
+    }
+
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.rotation_axis_aligned:
+                mCurrentObjectRotation = 0;
+                break;
+            case R.id.rotation_camera_aligned:
+                mCurrentObjectRotation = 1;
+                break;
+            case R.id.rotation_snap_aligned:
+                mCurrentObjectRotation = 2;
+                break;
+            case R.id.action:
+                mCurrentValue = 180;
+                JniInterface.onAction(mNativeApplication, mCurrentValue);
+                return true;
+            default:
+                return true;
+        }
+        JniInterface.onObjectRotationChanged(mNativeApplication, mCurrentObjectRotation);
+        return true;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // ARCore requires camera permissions to operate. If we did not yet obtain runtime
+        // permission on Android M and above, now is a good time to ask the user for it.
+        if (!CameraPermissionHelper.hasCameraPermission(this)) {
+            CameraPermissionHelper.requestCameraPermission(this);
+            return;
+        }
+
+        JniInterface.onResume(mNativeApplication, getApplicationContext(), this);
+        mSurfaceView.onResume();
+
+        mLoadingMessageSnackbar =
+                Snackbar.make(
+                        HelloArActivity.this.findViewById(android.R.id.content),
+                        "Searching for surfaces...",
+                        Snackbar.LENGTH_INDEFINITE);
+        // Set the snackbar background to light transparent black color.
+        mLoadingMessageSnackbar.getView().setBackgroundColor(0xbf323232);
+        mLoadingMessageSnackbar.show();
+        mPlaneStatusCheckingHandler.postDelayed(
+                mPlaneStatusCheckingRunnable, SNACKBAR_UPDATE_INTERVAL_MILLIS);
+
+        // Listen to display changed events to detect 180° rotation, which does not cause a config
+        // change or view resize.
+        getSystemService(DisplayManager.class).registerDisplayListener(this, null);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mSurfaceView.onPause();
+        JniInterface.onPause(mNativeApplication);
+
+        mPlaneStatusCheckingHandler.removeCallbacks(mPlaneStatusCheckingRunnable);
+
+        getSystemService(DisplayManager.class).unregisterDisplayListener(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        // Synchronized to avoid racing onDrawFrame.
+        synchronized (this) {
+            JniInterface.destroyNativeApplication(mNativeApplication);
+            mNativeApplication = 0;
+        }
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (hasFocus) {
+            // Standard Android full-screen functionality.
+            getWindow()
+                    .getDecorView()
+                    .setSystemUiVisibility(
+                            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                    | View.SYSTEM_UI_FLAG_FULLSCREEN
+                                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
+    @Override
+    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+        GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+        JniInterface.onGlSurfaceCreated(mNativeApplication);
+    }
+
+    @Override
+    public void onSurfaceChanged(GL10 gl, int width, int height) {
+        mViewportWidth = width;
+        mViewportHeight = height;
+        mViewportChanged = true;
+    }
+
+    @Override
+    public void onDrawFrame(GL10 gl) {
+        // Synchronized to avoid racing onDestroy.
+        synchronized (this) {
+            if (mNativeApplication == 0) {
+                return;
+            }
+            if (mViewportChanged) {
+                int displayRotation = getWindowManager().getDefaultDisplay().getRotation();
+                JniInterface.onDisplayGeometryChanged(
+                        mNativeApplication, displayRotation, mViewportWidth, mViewportHeight);
+                mViewportChanged = false;
+            }
+            JniInterface.onGlSurfaceDrawFrame(mNativeApplication);
+        }
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] results) {
+        if (!CameraPermissionHelper.hasCameraPermission(this)) {
+            Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG)
+                    .show();
+            if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
+                // Permission denied with checking "Do not ask again".
+                CameraPermissionHelper.launchPermissionSettings(this);
+            }
+            finish();
+        }
+    }
+
+    // DisplayListener methods
+    @Override
+    public void onDisplayAdded(int displayId) {
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        mViewportChanged = true;
+    }
+}
diff --git a/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java
new file mode 100644
index 0000000..1592bb2
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/java/org/skia/arcore/JniInterface.java
@@ -0,0 +1,82 @@
+package org.skia.arcore;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.opengl.GLUtils;
+import android.util.Log;
+
+import java.io.IOException;
+
+/**
+ * JNI interface to native layer.
+ */
+public class JniInterface {
+    static {
+        System.loadLibrary("hello_ar_native");
+    }
+
+    private static final String TAG = "JniInterface";
+    static AssetManager assetManager;
+
+    public static native long createNativeApplication(AssetManager assetManager);
+
+    public static native void destroyNativeApplication(long nativeApplication);
+
+    public static native void onPause(long nativeApplication);
+
+    public static native void onResume(long nativeApplication, Context context, Activity activity);
+
+    /**
+     * Allocate OpenGL resources for rendering.
+     */
+    public static native void onGlSurfaceCreated(long nativeApplication);
+
+    /**
+     * Called on the OpenGL thread before onGlSurfaceDrawFrame when the view port width, height, or
+     * display rotation may have changed.
+     */
+    public static native void onDisplayGeometryChanged(
+            long nativeApplication, int displayRotation, int width, int height);
+
+    public static native void onObjectRotationChanged(long nativeApplication, int rotation);
+
+    public static native void onAction(long nativeApplication, float value);
+
+    /**
+     * Main render loop, called on the OpenGL thread.
+     */
+    public static native void onGlSurfaceDrawFrame(long nativeApplication);
+
+    /**
+     * OnTouch event, called on the OpenGL thread.
+     */
+
+    public static native void onTouchTranslate(long nativeApplication, float x, float y);
+
+    public static native boolean onTouchedFirst(long nativeApplication, float x, float y, int drawMode);
+
+    public static native void onTouchedFinal(long nativeApplication, int type);
+
+
+    /**
+     * Get plane count in current session. Used to disable the "searching for surfaces" snackbar.
+     */
+    public static native boolean hasDetectedPlanes(long nativeApplication);
+
+    public static Bitmap loadImage(String imageName) {
+
+        try {
+            return BitmapFactory.decodeStream(assetManager.open(imageName));
+        } catch (IOException e) {
+            Log.e(TAG, "Cannot open image " + imageName);
+            return null;
+        }
+    }
+
+    public static void loadTexture(int target, Bitmap bitmap) {
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+    }
+}
diff --git a/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png b/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..3f691da
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml b/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml
new file mode 100644
index 0000000..49457c9
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/layout-xlarge-land/activity_main.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:background="@android:color/darker_gray"
+    android:layout_height="match_parent">
+
+    <!-- The navigation drawer that's always open -->
+    <ListView android:id="@+id/leftDrawer"
+        android:layout_width="240dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:choiceMode="singleChoice"
+        android:divider="@android:color/transparent"
+        android:dividerHeight="0dp"
+        android:layout_marginRight="5dp"
+        android:background="@android:color/background_light"/>
+
+    <!-- The main content view -->
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <!-- We use mainLayout for recreating SurfaceView -->
+        <LinearLayout
+            android:id="@+id/mainLayout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <SurfaceView
+                android:id="@+id/surfaceView"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_centerVertical="true"
+                android:layout_centerHorizontal="true" />
+        </LinearLayout>
+    </RelativeLayout>
+
+</LinearLayout>
+
diff --git a/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml b/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..c3f1e2a
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/layout/activity_main.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/drawerLayout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <!-- The main content view -->
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+        <View
+            android:layout_gravity="center"
+            android:id="@+id/menuView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/my_toolbar"
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize"
+                android:background="?attr/colorPrimary"
+                android:elevation="4dp"
+                android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
+                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
+            <EditText
+                android:id="@+id/text_box"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:focusable="false"
+                android:clickable="false"
+                android:visibility="gone"/>
+            <android.opengl.GLSurfaceView
+                android:id="@+id/surfaceview"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_gravity="bottom" />
+
+        </LinearLayout>
+
+
+
+    </FrameLayout>
+
+</android.support.v4.widget.DrawerLayout>
+
diff --git a/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml b/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml
new file mode 100644
index 0000000..7a7d539
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/layout/state_item.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical" android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:weightSum="1">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="10dp"
+        android:layout_marginLeft="10dp"
+        android:layout_marginBottom="0dp"
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:text="Name:"
+        android:id="@+id/nameText" />
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:layout_marginLeft="10dp"
+        android:layout_marginTop="0dp"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:text="Value"
+        android:id="@+id/valueText" />
+
+    <Spinner
+        android:id="@+id/optionSpinner"
+        android:paddingTop="0dp"
+        android:paddingBottom="0dp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+    </Spinner>
+
+</LinearLayout>
diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml b/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml
new file mode 100644
index 0000000..9285180
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/menu/draw_menu.xml
@@ -0,0 +1,15 @@
+<menu	xmlns:android="http://schemas.android.com/apk/res/android">
+	<item
+		android:id="@+id/draw_text"
+		android:title="Draw Text" />
+	<item
+		android:id="@+id/draw_shape"
+		android:title="Draw Shape" >
+		<menu>
+			<item android:id="@+id/draw_circle"
+				android:title="Circle" />
+			<item android:id="@+id/draw_rect"
+				android:title="Rectangle" />
+		</menu>
+	</item>
+</menu>
\ No newline at end of file
diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml b/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml
new file mode 100644
index 0000000..25f6a1d
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/menu/edit_menu.xml
@@ -0,0 +1,8 @@
+<menu	xmlns:android="http://schemas.android.com/apk/res/android">
+	<item
+		android:id="@+id/edit_size"
+		android:title="Size" />
+	<item
+		android:id="@+id/edit_text"
+		android:title="Text" />
+</menu>
\ No newline at end of file
diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml b/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml
new file mode 100644
index 0000000..bcee4f0
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/menu/rotation_mode.xml
@@ -0,0 +1,11 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+	<item android:id="@+id/rotation_axis_aligned"
+		android:title="Plane Axis-Aligned"/>
+	<item android:id="@+id/rotation_camera_aligned"
+		android:title="Camera-Aligned"/>
+	<item android:id="@+id/rotation_snap_aligned"
+		android:title="Snap-Aligned"/>
+
+	<item android:id="@+id/action"
+		android:title="Do Action"/>
+</menu>
\ No newline at end of file
diff --git a/platform_tools/android/apps/arcore/src/main/res/menu/title.xml b/platform_tools/android/apps/arcore/src/main/res/menu/title.xml
new file mode 100644
index 0000000..57d04ef
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/menu/title.xml
@@ -0,0 +1,12 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <item
+        android:id="@+id/action_left"
+        android:icon="@android:drawable/ic_media_previous"
+        android:showAsAction="always"/>
+
+    <item android:id="@+id/action_right"
+        android:icon="@android:drawable/ic_media_next"
+        android:showAsAction="always"/>
+
+</menu>
diff --git a/platform_tools/android/apps/arcore/src/main/res/values/integers.xml b/platform_tools/android/apps/arcore/src/main/res/values/integers.xml
new file mode 100644
index 0000000..d3da8e7
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/values/integers.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <integer name="value_tag_key">1</integer>
+</resources>
\ No newline at end of file
diff --git a/platform_tools/android/apps/arcore/src/main/res/values/strings.xml b/platform_tools/android/apps/arcore/src/main/res/values/strings.xml
new file mode 100644
index 0000000..8b7a8ad
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   Copyright 2017 Google Inc.
+
+   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.
+-->
+<resources>
+  <string name="app_name">HelloAR C</string>
+</resources>
diff --git a/platform_tools/android/apps/arcore/src/main/res/values/styles.xml b/platform_tools/android/apps/arcore/src/main/res/values/styles.xml
new file mode 100644
index 0000000..3a71bd3
--- /dev/null
+++ b/platform_tools/android/apps/arcore/src/main/res/values/styles.xml
@@ -0,0 +1,35 @@
+<!--
+   Copyright 2017 Google Inc.
+
+   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.
+-->
+<resources>
+
+  <!--
+      Base application theme, dependent on API level. This theme is replaced
+      by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+  -->
+  <style name="AppBaseTheme" parent="android:Theme.Light">
+    <!--
+        Theme customizations available in newer API levels can go in
+        res/values-vXX/styles.xml, while customizations related to
+        backward-compatibility can go here.
+    -->
+  </style>
+
+  <!-- Application theme. -->
+  <style name="AppTheme" parent="AppBaseTheme">
+    <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+  </style>
+
+</resources>
diff --git a/platform_tools/android/apps/build.gradle b/platform_tools/android/apps/build.gradle
index 9f285e5..64e7308 100644
--- a/platform_tools/android/apps/build.gradle
+++ b/platform_tools/android/apps/build.gradle
@@ -3,10 +3,11 @@
 
 buildscript {
     repositories {
+        google()
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.2.3'
+        classpath 'com.android.tools.build:gradle:3.0.1'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -15,6 +16,7 @@
 
 allprojects {
     repositories {
+        google()
         jcenter()
     }
 }
@@ -105,5 +107,6 @@
     }
 
     String out_dir = getVariantOutDir(project, variant).skiaOut
+
     return "${depotToolsDir}/ninja -C $out_dir $appName"
 }
diff --git a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
index 9326d8c..68afb72 100644
--- a/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
+++ b/platform_tools/android/apps/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
\ No newline at end of file
diff --git a/platform_tools/android/apps/settings.gradle b/platform_tools/android/apps/settings.gradle
index a8c2cb3..148e1ac 100644
--- a/platform_tools/android/apps/settings.gradle
+++ b/platform_tools/android/apps/settings.gradle
@@ -1,2 +1,3 @@
 include ':viewer'
 include ':skqp'
+include ':arcore' //must build out directory first: bin/gn gen out/arm64 --args='ndk="NDKPATH" target_cpu="ABI" is_component_build=true'
diff --git a/platform_tools/android/apps/skqp/build.gradle b/platform_tools/android/apps/skqp/build.gradle
index e368a66..2c54751 100644
--- a/platform_tools/android/apps/skqp/build.gradle
+++ b/platform_tools/android/apps/skqp/build.gradle
@@ -22,6 +22,7 @@
         versionName "1.0"
         signingConfig signingConfigs.debug
     }
+    flavorDimensions "base"
     sourceSets.main.jni.srcDirs = []
     sourceSets.main.jniLibs.srcDir "src/main/libs"
     productFlavors { universal{}; arm {}; arm64 {}; x86 {}; x64 {}; arm64vulkan{}; }
diff --git a/platform_tools/android/apps/viewer/build.gradle b/platform_tools/android/apps/viewer/build.gradle
index 630544d..6ec51c8 100644
--- a/platform_tools/android/apps/viewer/build.gradle
+++ b/platform_tools/android/apps/viewer/build.gradle
@@ -22,6 +22,7 @@
         versionName "1.0"
         signingConfig signingConfigs.debug
     }
+    flavorDimensions "base"
     sourceSets.main.jni.srcDirs = [] //disable automatic ndk-build call
     sourceSets.main.jniLibs.srcDir "src/main/libs"
     productFlavors { universal{}; arm {}; arm64 {}; x86 {}; x64 {}; arm64vulkan{}; }
diff --git a/platform_tools/libraries/include/arcore_c_api.h b/platform_tools/libraries/include/arcore_c_api.h
new file mode 100644
index 0000000..cecd117
--- /dev/null
+++ b/platform_tools/libraries/include/arcore_c_api.h
@@ -0,0 +1,2255 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef ARCORE_C_API_H_
+#define ARCORE_C_API_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+/// @defgroup concepts Concepts
+/// High-Level concepts of ARCore
+///
+/// @section ownership Object ownership
+///
+/// ARCore has two categories of objects: "value types" and "reference types".
+///
+/// - Value types are owned by application. They are created and destroyed using
+///   the @c create / @c destroy methods, and are populated by ARCore using
+///   methods with @c get in the method name.
+///
+/// - Reference types are owned by ARCore. A reference is acquired by one of the
+///   @c acquire methods.  For each call to the @c acquire method, the
+///   application must call the matching @c release method. Note that even if
+///   last reference is released, ARCore may continue to hold a reference to the
+///   object at ARCore's discretion.
+///
+/// Reference types are further split into:
+///
+/// - Long-lived objects. These objects persist across frames, possibly for the
+///   life span of the application or session. Acquire may fail if ARCore is in
+///   an incorrect state, e.g. not tracking. Acquire from list always succeeds,
+///   as the object already exists.
+///
+/// - Transient large data. These objects are usually acquired per-frame and are
+///   a limited resource. The @c acquire call may fail if the resource is
+///   exhausted (too many are currently held), deadline exceeded (the target of
+///   the acquire was already released), or the resource is not yet available.
+///
+/// Note: Lists are value types (owned by application), but can hold references
+/// to long-lived objects. This means that the references held by a list are not
+/// released until either the list is destroyed, or is re-populated by another
+/// api call.
+///
+/// For example, ::ArAnchorList, which is a value type, will hold references to
+/// Anchors, which are long-lived objects.
+///
+/// @section spaces Poses and Coordinate Spaces
+///
+/// An @c ArPose describes an rigid transformation from one coordinate space to
+/// another. As provided from all ARCore APIs, Poses always describe the
+/// transformation from object's local coordinate space to the <b>world
+/// coordinate space</b> (see below). That is, Poses from ARCore APIs can be
+/// thought of as equivalent to OpenGL model matrices.
+///
+/// The transformation is defined using a quaternion rotation about the origin
+/// followed by a translation.
+///
+/// The coordinate system is right-handed, like OpenGL conventions.
+///
+/// Translation units are meters.
+///
+/// @section worldcoordinates World Coordinate Space
+///
+/// As ARCore's understanding of the environment changes, it adjusts its model
+/// of the world to keep things consistent. When this happens, the numerical
+/// location (coordinates) of the camera and anchors can change significantly to
+/// maintain appropriate relative positions of the physical locations they
+/// represent.
+///
+/// These changes mean that every frame should be considered to be in a
+/// completely unique world coordinate space. The numerical coordinates of
+/// anchors and the camera should never be used outside the rendering frame
+/// during which they were retrieved. If a position needs to be considered
+/// beyond the scope of a single rendering frame, either an anchor should be
+/// created or a position relative to a nearby existing anchor should be used.
+
+/// @defgroup common Common Definitions
+/// Shared types and constants
+
+/// @defgroup anchor Anchor
+/// Describes a fixed location and orientation in the real world.
+
+/// @defgroup arcoreapk ArCoreApk
+/// Management of the ARCore service APK
+
+/// @defgroup augmented_image AugmentedImage
+/// An image being detected and tracked by ARCore.
+
+/// @defgroup augmented_image_database AugmentedImageDatabase
+/// Database containing a list of images to be detected and tracked by ARCore.
+
+/// @defgroup camera Camera
+/// Provides information about the camera that is used to capture images.
+
+/// @defgroup cloud Cloud Anchors
+/// The cloud state and configuration of an Anchor and the AR Session.
+
+/// @defgroup config Configuration
+/// Session configuration.
+
+/// @defgroup frame Frame
+/// Per-frame state.
+
+/// @defgroup hit HitResult
+/// Defines an intersection between a ray and estimated real-world geometry.
+
+/// @defgroup image ImageMetadata
+/// Provides access to metadata from the camera image capture result.
+
+/// @defgroup light LightEstimate
+/// Holds information about the estimated lighting of the real scene.
+
+/// @defgroup plane Plane
+/// Describes the current best knowledge of a real-world planar surface.
+
+/// @defgroup point Point
+/// Represents a point in space that ARCore is tracking.
+
+/// @defgroup pointcloud PointCloud
+/// Contains a set of observed 3D points and confidence values.
+
+/// @defgroup pose Pose
+/// Represents an immutable rigid transformation from one coordinate
+/// space to another.
+
+/// @defgroup session Session
+/// Session management.
+
+/// @defgroup trackable Trackable
+/// Something that can be tracked and that Anchors can be attached to.
+
+/// @defgroup cpp_helpers C++ helper functions
+
+/// @addtogroup config
+/// @{
+
+/// An opaque session configuration object (@ref ownership "value type").
+///
+/// Create with ArConfig_create()<br>
+/// Release with ArConfig_destroy()
+typedef struct ArConfig_ ArConfig;
+
+/// @}
+
+/// @addtogroup session
+/// @{
+
+/// The ArCore session (@ref ownership "value type").
+///
+/// Create with ArSession_create()<br>
+/// Release with ArSession_destroy()
+typedef struct ArSession_ ArSession;
+
+/// @}
+
+/// @addtogroup pose
+/// @{
+
+/// A structured rigid transformation (@ref ownership "value type").
+///
+/// Allocate with ArPose_create()<br>
+/// Release with ArPose_destroy()
+typedef struct ArPose_ ArPose;
+
+/// @}
+
+// Camera.
+
+/// @addtogroup camera
+/// @{
+
+/// The virtual and physical camera
+/// (@ref ownership "reference type, long-lived").
+///
+/// Acquire with ArFrame_acquireCamera()<br>
+/// Release with ArCamera_release()
+typedef struct ArCamera_ ArCamera;
+
+/// @}
+
+// Frame and frame objects.
+
+/// @addtogroup frame
+/// @{
+
+/// The world state resulting from an update (@ref ownership "value type").
+///
+/// Allocate with ArFrame_create()<br>
+/// Populate with ArSession_update()<br>
+/// Release with ArFrame_destroy()
+typedef struct ArFrame_ ArFrame;
+
+/// @}
+
+// LightEstimate.
+
+/// @addtogroup light
+/// @{
+
+/// An estimate of the real-world lighting (@ref ownership "value type").
+///
+/// Allocate with ArLightEstimate_create()<br>
+/// Populate with ArFrame_getLightEstimate()<br>
+/// Release with ArLightEstimate_destroy()
+typedef struct ArLightEstimate_ ArLightEstimate;
+
+/// @}
+
+// PointCloud.
+
+/// @addtogroup pointcloud
+/// @{
+
+/// A cloud of tracked 3D visual feature points
+/// (@ref ownership "reference type, large data").
+///
+/// Acquire with ArFrame_acquirePointCloud()<br>
+/// Release with ArPointCloud_release()
+typedef struct ArPointCloud_ ArPointCloud;
+
+/// @}
+
+// ImageMetadata.
+
+/// @addtogroup image
+/// @{
+
+/// Camera capture metadata (@ref ownership "reference type, large data").
+///
+/// Acquire with ArFrame_acquireImageMetadata()<br>
+/// Release with ArImageMetadata_release()
+typedef struct ArImageMetadata_ ArImageMetadata;
+
+/// Accessing CPU image from the tracking camera
+/// (@ref ownership "reference type, large data").
+///
+/// Acquire with ArFrame_acquireCameraImage()<br>
+/// Convert to NDK AImage with ArImage_getNdkImage()<br>
+/// Release with ArImage_release().
+typedef struct ArImage_ ArImage;
+
+/// Forward declaring the AImage struct from Android NDK, which is used
+/// in ArImage_getNdkImage().
+typedef struct AImage AImage;
+/// @}
+
+// Trackables.
+
+/// @addtogroup trackable
+/// @{
+
+/// Trackable base type (@ref ownership "reference type, long-lived").
+typedef struct ArTrackable_ ArTrackable;
+
+/// A list of ArTrackables (@ref ownership "value type").
+///
+/// Allocate with ArTrackableList_create()<br>
+/// Release with ArTrackableList_destroy()
+typedef struct ArTrackableList_ ArTrackableList;
+
+/// @}
+
+// Plane
+
+/// @addtogroup plane
+/// @{
+
+/// A detected planar surface (@ref ownership "reference type, long-lived").
+///
+/// Trackable type: #AR_TRACKABLE_PLANE <br>
+/// Release with: ArTrackable_release()
+typedef struct ArPlane_ ArPlane;
+
+/// @}
+
+// Point
+
+/// @addtogroup point
+/// @{
+
+/// An arbitrary point in space (@ref ownership "reference type, long-lived").
+///
+/// Trackable type: #AR_TRACKABLE_POINT <br>
+/// Release with: ArTrackable_release()
+typedef struct ArPoint_ ArPoint;
+
+/// @}
+
+// Augmented Image
+
+/// @addtogroup augmented_image
+/// @{
+
+/// An image that has been detected and tracked (@ref ownership "reference type,
+/// long-lived").
+///
+/// Trackable type: #AR_TRACKABLE_AUGMENTED_IMAGE <br>
+/// Release with: ArTrackable_release()
+typedef struct ArAugmentedImage_ ArAugmentedImage;
+
+/// @}
+
+// Augmented Image Database
+/// @addtogroup augmented_image_database
+/// @{
+
+/// A database of images to be detected and tracked by ARCore (@ref ownership
+/// "value type").
+///
+/// An image database supports up to 1000 images. A database can be generated by
+/// the `arcoreimg` command-line database generation tool provided in the SDK,
+/// or dynamically created at runtime by adding individual images.
+///
+/// Only one image database can be active in a session. Any images in the
+/// currently active image database that have a TRACKING/PAUSED state will
+/// immediately be set to the STOPPED state if a different or null image
+/// database is made active in the current session Config.
+///
+/// Create with ArAugmentedImageDatabase_create() or
+/// ArAugmentedImageDatabase_deserialize()<br>
+/// Release with: ArAugmentedImageDatabase_destroy()
+typedef struct ArAugmentedImageDatabase_ ArAugmentedImageDatabase;
+
+/// @}
+
+// Anchors.
+
+/// @addtogroup anchor
+/// @{
+
+/// A position in space attached to a trackable
+/// (@ref ownership "reference type, long-lived").
+///
+/// Create with ArSession_acquireNewAnchor() or
+///     ArHitResult_acquireNewAnchor()<br>
+/// Release with ArAnchor_release()
+typedef struct ArAnchor_ ArAnchor;
+
+/// A list of anchors (@ref ownership "value type").
+///
+/// Allocate with ArAnchorList_create()<br>
+/// Release with ArAnchorList_destroy()
+typedef struct ArAnchorList_ ArAnchorList;
+
+/// @}
+
+// Hit result functionality.
+
+/// @addtogroup hit
+/// @{
+
+/// A single trackable hit (@ref ownership "value type").
+///
+/// Allocate with ArHitResult_create()<br>
+/// Populate with ArHitResultList_getItem()<br>
+/// Release with ArHitResult_destroy()
+typedef struct ArHitResult_ ArHitResult;
+
+/// A list of hit test results (@ref ownership "value type").
+///
+/// Allocate with ArHitResultList_create()<br>
+/// Release with ArHitResultList_destroy()<br>
+typedef struct ArHitResultList_ ArHitResultList;
+
+/// @}
+
+/// @cond EXCLUDE_FROM_DOXYGEN
+
+// Forward declaring the ACameraMetadata struct from Android NDK, which is used
+// in ArImageMetadata_getNdkCameraMetadata
+typedef struct ACameraMetadata ACameraMetadata;
+
+/// @endcond
+
+/// @addtogroup cpp_helpers
+/// @{
+/// These methods expose allowable type conversions as C++ helper functions.
+/// This avoids having to explicitly @c reinterpret_cast in most cases.
+///
+/// Note: These methods only change the type of a pointer - they do not change
+/// the reference count of the referenced objects.
+///
+/// Note: There is no runtime checking that casts are correct. Call @ref
+/// ArTrackable_getType() beforehand to figure out the correct cast.
+
+#ifdef __cplusplus
+/// Upcasts to ArTrackable
+inline ArTrackable *ArAsTrackable(ArPlane *plane) {
+  return reinterpret_cast<ArTrackable *>(plane);
+}
+
+/// Upcasts to ArTrackable
+inline ArTrackable *ArAsTrackable(ArPoint *point) {
+  return reinterpret_cast<ArTrackable *>(point);
+}
+
+/// Upcasts to ArTrackable
+inline ArTrackable *ArAsTrackable(ArAugmentedImage *augmented_image) {
+  return reinterpret_cast<ArTrackable *>(augmented_image);
+}
+
+/// Downcasts to ArPlane.
+inline ArPlane *ArAsPlane(ArTrackable *trackable) {
+  return reinterpret_cast<ArPlane *>(trackable);
+}
+
+/// Downcasts to ArPoint.
+inline ArPoint *ArAsPoint(ArTrackable *trackable) {
+  return reinterpret_cast<ArPoint *>(trackable);
+}
+
+/// Downcasts to ArAugmentedImage.
+inline ArAugmentedImage *ArAsAugmentedImage(ArTrackable *trackable) {
+  return reinterpret_cast<ArAugmentedImage *>(trackable);
+}
+#endif
+/// @}
+
+// If compiling for C++11, use the 'enum underlying type' feature to enforce
+// size for ABI compatibility. In pre-C++11, use int32_t for fixed size.
+#if __cplusplus >= 201100
+#define AR_DEFINE_ENUM(_type) enum _type : int32_t
+#else
+#define AR_DEFINE_ENUM(_type) \
+  typedef int32_t _type;      \
+  enum
+#endif
+
+#if defined(__GNUC__) && !defined(AR_DEPRECATED_SUPPRESS)
+#define AR_DEPRECATED(_deprecation_string) \
+  __attribute__((deprecated(_deprecation_string)));
+#else
+#define AR_DEPRECATED(_deprecation_string)
+#endif
+
+/// @ingroup trackable
+/// Object types for heterogeneous query/update lists.
+AR_DEFINE_ENUM(ArTrackableType){
+    /// The base Trackable type. Can be passed to ArSession_getAllTrackables()
+    /// and ArFrame_getUpdatedTrackables() as the @c filter_type to get
+    /// all/updated Trackables of all types.
+    AR_TRACKABLE_BASE_TRACKABLE = 0x41520100,
+
+    /// The ::ArPlane subtype of Trackable.
+    AR_TRACKABLE_PLANE = 0x41520101,
+
+    /// The ::ArPoint subtype of Trackable.
+    AR_TRACKABLE_POINT = 0x41520102,
+
+    /// The ::ArAugmentedImage subtype of Trackable.
+    AR_TRACKABLE_AUGMENTED_IMAGE = 0x41520104,
+
+    /// An invalid Trackable type.
+    AR_TRACKABLE_NOT_VALID = 0};
+
+/// @ingroup common
+/// Return code indicating success or failure of a method.
+AR_DEFINE_ENUM(ArStatus){
+    /// The operation was successful.
+    AR_SUCCESS = 0,
+
+    /// One of the arguments was invalid, either null or not appropriate for the
+    /// operation requested.
+    AR_ERROR_INVALID_ARGUMENT = -1,
+
+    /// An internal error occurred that the application should not attempt to
+    /// recover from.
+    AR_ERROR_FATAL = -2,
+
+    /// An operation was attempted that requires the session be running, but the
+    /// session was paused.
+    AR_ERROR_SESSION_PAUSED = -3,
+
+    /// An operation was attempted that requires the session be paused, but the
+    /// session was running.
+    AR_ERROR_SESSION_NOT_PAUSED = -4,
+
+    /// An operation was attempted that the session be in the TRACKING state,
+    /// but the session was not.
+    AR_ERROR_NOT_TRACKING = -5,
+
+    /// A texture name was not set by calling ArSession_setCameraTextureName()
+    /// before the first call to ArSession_update()
+    AR_ERROR_TEXTURE_NOT_SET = -6,
+
+    /// An operation required GL context but one was not available.
+    AR_ERROR_MISSING_GL_CONTEXT = -7,
+
+    /// The configuration supplied to ArSession_configure() was unsupported.
+    /// To avoid this error, ensure that Session_checkSupported() returns true.
+    AR_ERROR_UNSUPPORTED_CONFIGURATION = -8,
+
+    /// The android camera permission has not been granted prior to calling
+    /// ArSession_resume()
+    AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED = -9,
+
+    /// Acquire failed because the object being acquired is already released.
+    /// For example, this happens if the application holds an ::ArFrame beyond
+    /// the next call to ArSession_update(), and then tries to acquire its point
+    /// cloud.
+    AR_ERROR_DEADLINE_EXCEEDED = -10,
+
+    /// There are no available resources to complete the operation.  In cases of
+    /// @c acquire methods returning this error, This can be avoided by
+    /// releasing previously acquired objects before acquiring new ones.
+    AR_ERROR_RESOURCE_EXHAUSTED = -11,
+
+    /// Acquire failed because the data isn't available yet for the current
+    /// frame. For example, acquire the image metadata may fail with this error
+    /// because the camera hasn't fully started.
+    AR_ERROR_NOT_YET_AVAILABLE = -12,
+
+    /// The android camera has been reallocated to a higher priority app or is
+    /// otherwise unavailable.
+    AR_ERROR_CAMERA_NOT_AVAILABLE = -13,
+
+    /// The host/resolve function call failed because the Session is not
+    /// configured for cloud anchors.
+    AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED = -14,
+
+    /// ArSession_configure() failed because the specified configuration
+    /// required the Android INTERNET permission, which the application did not
+    /// have.
+    AR_ERROR_INTERNET_PERMISSION_NOT_GRANTED = -15,
+
+    /// HostCloudAnchor() failed because the anchor is not a type of anchor that
+    /// is currently supported for hosting.
+    AR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_HOSTING = -16,
+
+    /// An image with insufficient quality (e.g. too few features) was attempted
+    /// to be added to the image database.
+    AR_ERROR_IMAGE_INSUFFICIENT_QUALITY = -17,
+
+    /// The data passed in for this operation was not in a valid format.
+    AR_ERROR_DATA_INVALID_FORMAT = -18,
+
+    /// The data passed in for this operation is not supported by this version
+    /// of the SDK.
+    AR_ERROR_DATA_UNSUPPORTED_VERSION = -19,
+
+    /// The ARCore APK is not installed on this device.
+    AR_UNAVAILABLE_ARCORE_NOT_INSTALLED = -100,
+
+    /// The device is not currently compatible with ARCore.
+    AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE = -101,
+
+    /// The ARCore APK currently installed on device is too old and needs to be
+    /// updated.
+    AR_UNAVAILABLE_APK_TOO_OLD = -103,
+
+    /// The ARCore APK currently installed no longer supports the ARCore SDK
+    /// that the application was built with.
+    AR_UNAVAILABLE_SDK_TOO_OLD = -104,
+
+    /// The user declined installation of the ARCore APK during this run of the
+    /// application and the current request was not marked as user-initiated.
+    AR_UNAVAILABLE_USER_DECLINED_INSTALLATION = -105};
+
+/// @ingroup common
+/// Describes the tracking state of a @c Trackable, an ::ArAnchor or the
+/// ::ArCamera.
+AR_DEFINE_ENUM(ArTrackingState){
+    /// The object is currently tracked and its pose is current.
+    AR_TRACKING_STATE_TRACKING = 0,
+
+    /// ARCore has paused tracking this object, but may resume tracking it in
+    /// the future. This can happen if device tracking is lost, if the user
+    /// enters a new space, or if the Session is currently paused. When in this
+    /// state, the positional properties of the object may be wildly inaccurate
+    /// and should not be used.
+    AR_TRACKING_STATE_PAUSED = 1,
+
+    /// ARCore has stopped tracking this Trackable and will never resume
+    /// tracking it.
+    AR_TRACKING_STATE_STOPPED = 2};
+
+/// @ingroup cloud
+/// Describes the current cloud state of an @c Anchor.
+AR_DEFINE_ENUM(ArCloudAnchorState){
+    /// The anchor is purely local. It has never been hosted using
+    /// hostCloudAnchor, and has not been acquired using acquireCloudAnchor.
+    AR_CLOUD_ANCHOR_STATE_NONE = 0,
+
+    /// A hosting/resolving task for the anchor is in progress. Once the task
+    /// completes in the background, the anchor will get a new cloud state after
+    /// the next update() call.
+    AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS = 1,
+
+    /// A hosting/resolving task for this anchor completed successfully.
+    AR_CLOUD_ANCHOR_STATE_SUCCESS = 2,
+
+    /// A hosting/resolving task for this anchor finished with an internal
+    /// error. The app should not attempt to recover from this error.
+    AR_CLOUD_ANCHOR_STATE_ERROR_INTERNAL = -1,
+
+    /// The app cannot communicate with the ARCore Cloud because of an invalid
+    /// or unauthorized API key in the manifest, or because there was no API key
+    /// present in the manifest.
+    AR_CLOUD_ANCHOR_STATE_ERROR_NOT_AUTHORIZED = -2,
+
+    /// The ARCore Cloud was unreachable. This can happen because of a number of
+    /// reasons. The request sent to the server could have timed out with no
+    /// response, there could be a bad network connection, DNS unavailability,
+    /// firewall issues, or anything that could affect the device's ability to
+    /// connect to the ARCore Cloud.
+    AR_CLOUD_ANCHOR_STATE_ERROR_SERVICE_UNAVAILABLE = -3,
+
+    /// The application has exhausted the request quota allotted to the given
+    /// API key. The developer should request additional quota for the ARCore
+    /// Cloud for their API key from the Google Developers Console.
+    AR_CLOUD_ANCHOR_STATE_ERROR_RESOURCE_EXHAUSTED = -4,
+
+    /// Hosting failed, because the server could not successfully process the
+    /// dataset for the given anchor. The developer should try again after the
+    /// device has gathered more data from the environment.
+    AR_CLOUD_ANCHOR_STATE_ERROR_HOSTING_DATASET_PROCESSING_FAILED = -5,
+
+    /// Resolving failed, because the ARCore Cloud could not find the provided
+    /// cloud anchor ID.
+    AR_CLOUD_ANCHOR_STATE_ERROR_CLOUD_ID_NOT_FOUND = -6,
+
+    /// The server could not match the visual features provided by ARCore
+    /// against the localization dataset of the requested cloud anchor ID. This
+    /// means that the anchor pose being requested was likely not created in the
+    /// user's surroundings.
+    AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_LOCALIZATION_NO_MATCH = -7,
+
+    /// The anchor could not be resolved because the SDK used to host the anchor
+    /// was newer than and incompatible with the version being used to acquire
+    /// it.
+    AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_SDK_VERSION_TOO_OLD = -8,
+
+    /// The anchor could not be acquired because the SDK used to host the anchor
+    /// was older than and incompatible with the version being used to acquire
+    /// it.
+    AR_CLOUD_ANCHOR_STATE_ERROR_RESOLVING_SDK_VERSION_TOO_NEW = -9};
+
+/// @ingroup arcoreapk
+/// Describes the current state of ARCore availability on the device.
+AR_DEFINE_ENUM(ArAvailability){
+    /// An internal error occurred while determining ARCore availability.
+    AR_AVAILABILITY_UNKNOWN_ERROR = 0,
+    /// ARCore is not installed, and a query has been issued to check if ARCore
+    /// is is supported.
+    AR_AVAILABILITY_UNKNOWN_CHECKING = 1,
+    /// ARCore is not installed, and the query to check if ARCore is supported
+    /// timed out. This may be due to the device being offline.
+    AR_AVAILABILITY_UNKNOWN_TIMED_OUT = 2,
+    /// ARCore is not supported on this device.
+    AR_AVAILABILITY_UNSUPPORTED_DEVICE_NOT_CAPABLE = 100,
+    /// The device and Android version are supported, but the ARCore APK is not
+    /// installed.
+    AR_AVAILABILITY_SUPPORTED_NOT_INSTALLED = 201,
+    /// The device and Android version are supported, and a version of the
+    /// ARCore APK is installed, but that ARCore APK version is too old.
+    AR_AVAILABILITY_SUPPORTED_APK_TOO_OLD = 202,
+    /// ARCore is supported, installed, and available to use.
+    AR_AVAILABILITY_SUPPORTED_INSTALLED = 203};
+
+/// @ingroup arcoreapk
+/// Indicates the outcome of a call to ArCoreApk_requestInstall().
+AR_DEFINE_ENUM(ArInstallStatus){
+    /// The requested resource is already installed.
+    AR_INSTALL_STATUS_INSTALLED = 0,
+    /// Installation of the resource was requested. The current activity will be
+    /// paused.
+    AR_INSTALL_STATUS_INSTALL_REQUESTED = 1};
+
+/// @ingroup arcoreapk
+/// Controls the behavior of the installation UI.
+AR_DEFINE_ENUM(ArInstallBehavior){
+    /// Hide the Cancel button during initial prompt and prevent user from
+    /// exiting via tap-outside.
+    ///
+    /// Note: The BACK button or tapping outside of any marketplace-provided
+    /// install dialog will still decline the installation.
+    AR_INSTALL_BEHAVIOR_REQUIRED = 0,
+    /// Include Cancel button in initial prompt and allow easily backing out
+    /// after installation has been initiated.
+    AR_INSTALL_BEHAVIOR_OPTIONAL = 1};
+
+/// @ingroup arcoreapk
+/// Controls the message displayed by the installation UI.
+AR_DEFINE_ENUM(ArInstallUserMessageType){
+    /// Display a localized message like "This application requires ARCore...".
+    AR_INSTALL_USER_MESSAGE_TYPE_APPLICATION = 0,
+    /// Display a localized message like "This feature requires ARCore...".
+    AR_INSTALL_USER_MESSAGE_TYPE_FEATURE = 1,
+    /// Application has explained why ARCore is required prior to calling
+    /// ArCoreApk_requestInstall(), skip user education dialog.
+    AR_INSTALL_USER_MESSAGE_TYPE_USER_ALREADY_INFORMED = 2};
+
+/// @ingroup config
+/// Select the behavior of the lighting estimation subsystem.
+AR_DEFINE_ENUM(ArLightEstimationMode){
+    /// Lighting estimation is disabled.
+    AR_LIGHT_ESTIMATION_MODE_DISABLED = 0,
+    /// Lighting estimation is enabled, generating a single-value intensity
+    /// estimate.
+    AR_LIGHT_ESTIMATION_MODE_AMBIENT_INTENSITY = 1};
+
+/// @ingroup config
+/// Select the behavior of the plane detection subsystem.
+AR_DEFINE_ENUM(ArPlaneFindingMode){
+    /// Plane detection is disabled.
+    AR_PLANE_FINDING_MODE_DISABLED = 0,
+    /// Detection of only horizontal planes is enabled.
+    AR_PLANE_FINDING_MODE_HORIZONTAL = 1,
+    /// Detection of only vertical planes is enabled.
+    AR_PLANE_FINDING_MODE_VERTICAL = 2,
+    /// Detection of horizontal and vertical planes is enabled.
+    AR_PLANE_FINDING_MODE_HORIZONTAL_AND_VERTICAL = 3};
+
+/// @ingroup config
+/// Selects the behavior of ArSession_update().
+AR_DEFINE_ENUM(ArUpdateMode){
+    /// @c update() will wait until a new camera image is available, or until
+    /// the built-in timeout (currently 66ms) is reached. On most
+    /// devices the camera is configured to capture 30 frames per second.
+    /// If the camera image does not arrive by the built-in timeout, then
+    /// @c update() will return the most recent ::ArFrame object.
+    AR_UPDATE_MODE_BLOCKING = 0,
+    /// @c update() will return immediately without blocking. If no new camera
+    /// image is available, then @c update() will return the most recent
+    /// ::ArFrame object.
+    AR_UPDATE_MODE_LATEST_CAMERA_IMAGE = 1};
+
+/// @ingroup plane
+/// Simple summary of the normal vector of a plane, for filtering purposes.
+AR_DEFINE_ENUM(ArPlaneType){
+    /// A horizontal plane facing upward (for example a floor or tabletop).
+    AR_PLANE_HORIZONTAL_UPWARD_FACING = 0,
+    /// A horizontal plane facing downward (for example a ceiling).
+    AR_PLANE_HORIZONTAL_DOWNWARD_FACING = 1,
+    /// A vertical plane (for example a wall).
+    AR_PLANE_VERTICAL = 2};
+
+/// @ingroup light
+/// Tracks the validity of a light estimate.
+AR_DEFINE_ENUM(ArLightEstimateState){
+    /// The light estimate is not valid this frame and should not be used for
+    /// rendering.
+    AR_LIGHT_ESTIMATE_STATE_NOT_VALID = 0,
+    /// The light estimate is valid this frame.
+    AR_LIGHT_ESTIMATE_STATE_VALID = 1};
+
+/// @ingroup point
+/// Indicates the orientation mode of the ::ArPoint.
+AR_DEFINE_ENUM(ArPointOrientationMode){
+    /// The orientation of the ::ArPoint is initialized to identity but may
+    /// adjust slightly over time.
+    AR_POINT_ORIENTATION_INITIALIZED_TO_IDENTITY = 0,
+    /// The orientation of the ::ArPoint will follow the behavior described in
+    /// ArHitResult_getHitPose().
+    AR_POINT_ORIENTATION_ESTIMATED_SURFACE_NORMAL = 1};
+
+/// @ingroup cloud
+/// Indicates the cloud configuration of the ::ArSession.
+AR_DEFINE_ENUM(ArCloudAnchorMode){
+    /// Anchor Hosting is disabled. This is the value set in the default
+    /// ::ArConfig.
+    AR_CLOUD_ANCHOR_MODE_DISABLED = 0,
+    /// Anchor Hosting is enabled. Setting this value and calling @c configure()
+    /// will require that the application have the Android INTERNET permission.
+    AR_CLOUD_ANCHOR_MODE_ENABLED = 1};
+
+#undef AR_DEFINE_ENUM
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Note: destroy methods do not take ArSession* to allow late destruction in
+// finalizers of garbage-collected languages such as Java.
+
+/// @addtogroup arcoreapk
+/// @{
+
+/// Determines if ARCore is supported on this device. This may initiate a query
+/// with a remote service to determine if the device is compatible, in which
+/// case it will return immediately with @c out_availability set to
+/// #AR_AVAILABILITY_UNKNOWN_CHECKING.
+///
+/// For ARCore-required apps (as indicated by the <a
+/// href="https://developers.google.com/ar/develop/c/enable-arcore#ar_required">manifest
+/// meta-data</a>) this method will assume device compatibility and will always
+/// immediately return one of #AR_AVAILABILITY_SUPPORTED_INSTALLED,
+/// #AR_AVAILABILITY_SUPPORTED_APK_TOO_OLD, or
+/// #AR_AVAILABILITY_SUPPORTED_NOT_INSTALLED.
+///
+/// Note: A result #AR_AVAILABILITY_SUPPORTED_INSTALLED only indicates presence
+/// of a suitably versioned ARCore APK. Session creation may still fail if the
+/// ARCore APK has been sideloaded onto an incompatible device.
+///
+/// May be called prior to ArSession_create().
+///
+/// @param[in] env The application's @c JNIEnv object
+/// @param[in] application_context A @c jobject referencing the application's
+///     Android @c Context.
+/// @param[out] out_availability A pointer to an ArAvailability to receive
+///     the result.
+void ArCoreApk_checkAvailability(void *env,
+                                 void *application_context,
+                                 ArAvailability *out_availability);
+
+/// Initiates installation of ARCore if needed. When your apllication launches
+/// or enters an AR mode, it should call this method with @c
+/// user_requested_install = 1.
+///
+/// If ARCore is installed and compatible, this function will set @c
+/// out_install_status to #AR_INSTALL_STATUS_INSTALLED.
+///
+/// If ARCore is not currently installed or the installed version not
+/// compatible, the function will set @c out_install_status to
+/// #AR_INSTALL_STATUS_INSTALL_REQUESTED and return immediately. Your current
+/// activity will then pause while the user is informed about the requierment of
+/// ARCore and offered the opportunity to install it.
+///
+/// When your activity resumes, you should call this method again, this time
+/// with @c user_requested_install = 0. This will either set
+/// @c out_install_status to #AR_INSTALL_STATUS_INSTALLED or return an error
+/// code indicating the reason that installation could not be completed.
+///
+/// ARCore-optional applications must ensure that ArCoreApk_checkAvailability()
+/// returns one of the <tt>AR_AVAILABILITY_SUPPORTED_...</tt> values before
+/// calling this method.
+///
+/// See <A
+/// href="https://github.com/google-ar/arcore-android-sdk/tree/master/samples">
+/// our sample code</A> for an example of how an ARCore-required application
+/// should use this function.
+///
+/// May be called prior to ArSession_create().
+///
+/// For more control over the message displayed and ease of exiting the process,
+/// see ArCoreApk_requestInstallCustom().
+///
+/// <b>Caution:</b> The value of <tt>*out_install_status</tt> should only be
+/// considered when #AR_SUCCESS is returned.  Otherwise this value must be
+/// ignored.
+///
+/// @param[in] env The application's @c JNIEnv object
+/// @param[in] application_activity A @c jobject referencing the application's
+///     current Android @c Activity.
+/// @param[in] user_requested_install if set, override the previous installation
+///     failure message and always show the installation interface.
+/// @param[out] out_install_status A pointer to an ArInstallStatus to receive
+///     the resulting install status, if successful.  Note: this value is only
+///     valid with the return value is #AR_SUCCESS.
+/// @return #AR_SUCCESS, or any of:
+/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting
+///     installation
+/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported
+///     on this device.
+/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined
+///     installation.
+ArStatus ArCoreApk_requestInstall(void *env,
+                                  void *application_activity,
+                                  bool user_requested_install,
+                                  ArInstallStatus *out_install_status);
+
+/// Initiates installation of ARCore if required, with configurable behavior.
+///
+/// This is a more flexible version of ArCoreApk_requestInstall() allowing the
+/// application control over the initial informational dialog and ease of
+/// exiting or cancelling the installation.
+///
+/// See ArCoreApk_requestInstall() for details of use and behavior.
+///
+/// May be called prior to ArSession_create().
+///
+/// @param[in] env The application's @c JNIEnv object
+/// @param[in] application_activity A @c jobject referencing the application's
+///     current Android @c Activity.
+/// @param[in] user_requested_install if set, override the previous installation
+///     failure message and always show the installation interface.
+/// @param[in] install_behavior controls the presence of the cancel button at
+///     the user education screen and if tapping outside the education screen or
+///     install-in-progress screen causes them to dismiss.
+/// @param[in] message_type controls the text of the of message displayed
+///     before showing the install prompt, or disables display of this message.
+/// @param[out] out_install_status A pointer to an ArInstallStatus to receive
+///     the resulting install status, if successful.  Note: this value is only
+///     valid with the return value is #AR_SUCCESS.
+/// @return #AR_SUCCESS, or any of:
+/// - #AR_ERROR_FATAL if an error occurs while checking for or requesting
+///     installation
+/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE if ARCore is not supported
+///     on this device.
+/// - #AR_UNAVAILABLE_USER_DECLINED_INSTALLATION if the user previously declined
+///     installation.
+ArStatus ArCoreApk_requestInstallCustom(void *env,
+                                        void *application_activity,
+                                        int32_t user_requested_install,
+                                        ArInstallBehavior install_behavior,
+                                        ArInstallUserMessageType message_type,
+                                        ArInstallStatus *out_install_status);
+
+/// @}
+/// @addtogroup session
+/// @{
+
+/// Attempts to create a new ARCore session.
+///
+/// This is the entry point of ARCore.  This function MUST be the first ARCore
+/// call made by an application.
+///
+/// @param[in]  env                 The application's @c JNIEnv object
+/// @param[in]  application_context A @c jobject referencing the application's
+///     Android @c Context
+/// @param[out] out_session_pointer A pointer to an @c ArSession* to receive
+///     the address of the newly allocated session.
+/// @return #AR_SUCCESS or any of:
+/// - #AR_UNAVAILABLE_ARCORE_NOT_INSTALLED
+/// - #AR_UNAVAILABLE_DEVICE_NOT_COMPATIBLE
+/// - #AR_UNAVAILABLE_APK_TOO_OLD
+/// - #AR_UNAVAILABLE_SDK_TOO_OLD
+/// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED
+ArStatus ArSession_create(void *env,
+                          void *application_context,
+                          ArSession **out_session_pointer);
+
+/// @}
+
+// === ArConfig methods ===
+
+/// @addtogroup config
+/// @{
+
+/// Creates a new configuration object and initializes it to a sensible default
+/// configuration. Plane detection and lighting estimation are enabled, and
+/// blocking update is selected. This configuration is guaranteed to be
+/// supported on all devices that support ARCore.
+void ArConfig_create(const ArSession *session, ArConfig **out_config);
+
+/// Releases memory used by the provided configuration object.
+void ArConfig_destroy(ArConfig *config);
+
+/// Stores the currently configured lighting estimation mode into
+/// @c *light_estimation_mode.
+void ArConfig_getLightEstimationMode(
+    const ArSession *session,
+    const ArConfig *config,
+    ArLightEstimationMode *light_estimation_mode);
+
+/// Sets the lighting estimation mode that should be used. See
+/// ::ArLightEstimationMode for available options.
+void ArConfig_setLightEstimationMode(
+    const ArSession *session,
+    ArConfig *config,
+    ArLightEstimationMode light_estimation_mode);
+
+/// Stores the currently configured plane finding mode into
+/// @c *plane_finding_mode.
+void ArConfig_getPlaneFindingMode(const ArSession *session,
+                                  const ArConfig *config,
+                                  ArPlaneFindingMode *plane_finding_mode);
+
+/// Sets the plane finding mode that should be used. See
+/// ::ArPlaneFindingMode for available options.
+void ArConfig_setPlaneFindingMode(const ArSession *session,
+                                  ArConfig *config,
+                                  ArPlaneFindingMode plane_finding_mode);
+
+/// Stores the currently configured behavior of @ref ArSession_update() into
+/// @c *update_mode.
+void ArConfig_getUpdateMode(const ArSession *session,
+                            const ArConfig *config,
+                            ArUpdateMode *update_mode);
+
+/// Sets the behavior of @ref ArSession_update(). See
+/// ::ArUpdateMode for available options.
+void ArConfig_setUpdateMode(const ArSession *session,
+                            ArConfig *config,
+                            ArUpdateMode update_mode);
+
+/// Gets the current cloud anchor mode from the ::ArConfig.
+void ArConfig_getCloudAnchorMode(const ArSession *session,
+                                 const ArConfig *config,
+                                 ArCloudAnchorMode *out_cloud_anchor_mode);
+
+/// Sets the cloud configuration that should be used. See ::ArCloudAnchorMode
+/// for available options.
+void ArConfig_setCloudAnchorMode(const ArSession *session,
+                                 ArConfig *config,
+                                 ArCloudAnchorMode cloud_anchor_mode);
+
+/// Sets the image database in the session configuration.
+///
+/// Any images in the currently active image database that have a
+/// TRACKING/PAUSED state will immediately be set to the STOPPED state if a
+/// different or null image database is set.
+///
+/// This function makes a copy of the image database.
+void ArConfig_setAugmentedImageDatabase(
+    const ArSession *session,
+    ArConfig *config,
+    const ArAugmentedImageDatabase *augmented_image_database);
+
+/// Returns the image database from the session configuration.
+///
+/// This function returns a copy of the internally stored image database.
+void ArConfig_getAugmentedImageDatabase(
+    const ArSession *session,
+    const ArConfig *config,
+    ArAugmentedImageDatabase *out_augmented_image_database);
+
+/// @}
+
+// === ArSession methods ===
+
+/// @addtogroup session
+/// @{
+
+/// Releases resources used by an ARCore session.
+void ArSession_destroy(ArSession *session);
+
+/// Before release 1.2.0: Checks if the provided configuration is usable on the
+/// this device. If this method returns #AR_ERROR_UNSUPPORTED_CONFIGURATION,
+/// calls to ArSession_configure(Config) with this configuration will fail.
+///
+/// This function now always returns true. See documentation for each
+/// configuration entry to know which configuration options & combinations are
+/// supported.
+///
+/// @param[in] session The ARCore session
+/// @param[in] config  The configuration to test
+/// @return #AR_SUCCESS or:
+///  - #AR_ERROR_INVALID_ARGUMENT if any of the arguments are null.
+/// @deprecated in release 1.2.0. Please refer to the release notes
+/// (<a
+/// href="https://github.com/google-ar/arcore-android-sdk/releases/tag/v1.2.0">release
+/// notes 1.2.0</a>)
+///
+ArStatus ArSession_checkSupported(const ArSession *session,
+                                  const ArConfig *config)
+    AR_DEPRECATED(
+        "deprecated in release 1.2.0. Please see function documentation");
+
+/// Configures the session with the given config.
+/// Note: a session is always initially configured with the default config.
+/// This should be called if a configuration different than default is needed.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_FATAL
+/// - #AR_ERROR_UNSUPPORTED_CONFIGURATION
+ArStatus ArSession_configure(ArSession *session, const ArConfig *config);
+
+/// Starts or resumes the ARCore Session.
+///
+/// Typically this should be called from <a
+/// href="https://developer.android.com/reference/android/app/Activity.html#onResume()"
+/// ><tt>Activity.onResume()</tt></a>.
+///
+/// @returns #AR_SUCCESS or any of:
+/// - #AR_ERROR_FATAL
+/// - #AR_ERROR_CAMERA_PERMISSION_NOT_GRANTED
+/// - #AR_ERROR_CAMERA_NOT_AVAILABLE
+ArStatus ArSession_resume(ArSession *session);
+
+/// Pause the current session. This method will stop the camera feed and release
+/// resources. The session can be restarted again by calling ArSession_resume().
+///
+/// Typically this should be called from <a
+/// href="https://developer.android.com/reference/android/app/Activity.html#onPause()"
+/// ><tt>Activity.onPause()</tt></a>.
+///
+/// @returns #AR_SUCCESS or any of:
+/// - #AR_ERROR_FATAL
+ArStatus ArSession_pause(ArSession *session);
+
+/// Sets the OpenGL texture name (id) that will allow GPU access to the camera
+/// image. The provided ID should have been created with @c glGenTextures(). The
+/// resulting texture must be bound to the @c GL_TEXTURE_EXTERNAL_OES target for
+/// use. Shaders accessing this texture must use a @c samplerExternalOES
+/// sampler. See sample code for an example.
+void ArSession_setCameraTextureName(ArSession *session, uint32_t texture_id);
+
+/// Sets the aspect ratio, coordinate scaling, and display rotation. This data
+/// is used by UV conversion, projection matrix generation, and hit test logic.
+///
+/// Note: this function doesn't fail. If given invalid input, it logs a error
+/// and doesn't apply the changes.
+///
+/// @param[in] session   The ARCore session
+/// @param[in] rotation  Display rotation specified by @c android.view.Surface
+///     constants: @c ROTATION_0, @c ROTATION_90, @c ROTATION_180 and
+///     @c ROTATION_270
+/// @param[in] width     Width of the view, in pixels
+/// @param[in] height    Height of the view, in pixels
+void ArSession_setDisplayGeometry(ArSession *session,
+                                  int32_t rotation,
+                                  int32_t width,
+                                  int32_t height);
+
+/// Updates the state of the ARCore system. This includes: receiving a new
+/// camera frame, updating the location of the device, updating the location of
+/// tracking anchors, updating detected planes, etc.
+///
+/// This call may cause off-screen OpenGL activity. Because of this, to avoid
+/// unnecessary frame buffer flushes and reloads, this call should not be made
+/// in the middle of rendering a frame or offscreen buffer.
+///
+/// This call may update the pose of all created anchors and detected planes.
+/// The set of updated objects is accessible through
+/// ArFrame_getUpdatedTrackables().
+///
+/// @c update() in blocking mode (see ::ArUpdateMode) will wait until a
+/// new camera image is available, or until the built-in timeout
+/// (currently 66ms) is reached.
+/// If the camera image does not arrive by the built-in timeout, then
+/// @c update() will return the most recent ::ArFrame object. For some
+/// applications it may be important to know if a new frame was actually
+/// obtained (for example, to avoid redrawing if the camera did not produce a
+/// new frame). To do that, compare the current frame's timestamp, obtained via
+/// @c ArFrame_getTimestamp, with the previously recorded frame timestamp. If
+/// they are different, this is a new frame.
+///
+/// @param[in]    session   The ARCore session
+/// @param[inout] out_frame The Frame object to populate with the updated world
+///     state.  This frame must have been previously created using
+///     ArFrame_create().  The same ArFrame instance may be used when calling
+///     this repeatedly.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_FATAL
+/// - #AR_ERROR_SESSION_PAUSED
+/// - #AR_ERROR_TEXTURE_NOT_SET
+/// - #AR_ERROR_MISSING_GL_CONTEXT
+/// - #AR_ERROR_CAMERA_NOT_AVAILABLE - camera was removed during runtime.
+ArStatus ArSession_update(ArSession *session, ArFrame *out_frame);
+
+/// Defines a tracked location in the physical world.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_NOT_TRACKING
+/// - #AR_ERROR_SESSION_PAUSED
+/// - #AR_ERROR_RESOURCE_EXHAUSTED
+ArStatus ArSession_acquireNewAnchor(ArSession *session,
+                                    const ArPose *pose,
+                                    ArAnchor **out_anchor);
+
+/// Returns all known anchors, including those not currently tracked. Anchors
+/// forgotten by ARCore due to a call to ArAnchor_detach() or entering the
+/// #AR_TRACKING_STATE_STOPPED state will not be included.
+///
+/// @param[in]    session         The ARCore session
+/// @param[inout] out_anchor_list The list to fill.  This list must have already
+///     been allocated with ArAnchorList_create().  If previously used, the list
+///     will first be cleared.
+void ArSession_getAllAnchors(const ArSession *session,
+                             ArAnchorList *out_anchor_list);
+
+/// Returns the list of all known @ref trackable "trackables".  This includes
+/// ::ArPlane objects if plane detection is enabled, as well as ::ArPoint
+/// objects created as a side effect of calls to ArSession_acquireNewAnchor() or
+/// ArFrame_hitTest().
+///
+/// @param[in]    session            The ARCore session
+/// @param[in]    filter_type        The type(s) of trackables to return.  See
+///     ::ArTrackableType for legal values.
+/// @param[inout] out_trackable_list The list to fill.  This list must have
+///     already been allocated with ArTrackableList_create().  If previously
+///     used, the list will first be cleared.
+void ArSession_getAllTrackables(const ArSession *session,
+                                ArTrackableType filter_type,
+                                ArTrackableList *out_trackable_list);
+
+/// This will create a new cloud anchor using pose and other metadata from
+/// @c anchor.
+///
+/// If the function returns #AR_SUCCESS, the cloud state of @c out_cloud_anchor
+/// will be set to #AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS and the initial pose
+/// will be set to the pose of @c anchor. However, the new @c out_cloud_anchor
+/// is completely independent of @c anchor, and the poses may diverge over time.
+/// If the return value of this function is not #AR_SUCCESS, then
+/// @c out_cloud_anchor will be set to null.
+///
+/// @param[in]    session          The ARCore session
+/// @param[in]    anchor           The anchor to be hosted
+/// @param[inout] out_cloud_anchor The new cloud anchor
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_NOT_TRACKING
+/// - #AR_ERROR_SESSION_PAUSED
+/// - #AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED
+/// - #AR_ERROR_RESOURCE_EXHAUSTED
+/// - #AR_ERROR_ANCHOR_NOT_SUPPORTED_FOR_HOSTING
+ArStatus ArSession_hostAndAcquireNewCloudAnchor(ArSession *session,
+                                                const ArAnchor *anchor,
+                                                ArAnchor **out_cloud_anchor);
+
+/// This will create a new cloud anchor, and schedule a resolving task to
+/// resolve the anchor's pose using the given cloud anchor ID.
+///
+/// If this function returns #AR_SUCCESS, the cloud state of @c out_cloud_anchor
+/// will be #AR_CLOUD_STATE_TASK_IN_PROGRESS, and its tracking state will be
+/// #AR_TRACKING_STATE_PAUSED. This anchor will never start tracking until its
+/// pose has been successfully resolved. If the resolving task ends in an error,
+/// the tracking state will be set to #AR_TRACKING_STATE_STOPPED. If the return
+/// value is not #AR_SUCCESS, then @c out_cloud_anchor will be set to null.
+///
+/// @param[in]    session          The ARCore session
+/// @param[in]    cloud_anchor_id  The cloud ID of the anchor to be resolved
+/// @param[inout] out_cloud_anchor The new cloud anchor
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_NOT_TRACKING
+/// - #AR_ERROR_SESSION_PAUSED
+/// - #AR_ERROR_CLOUD_ANCHORS_NOT_CONFIGURED
+/// - #AR_ERROR_RESOURCE_EXHAUSTED
+ArStatus ArSession_resolveAndAcquireNewCloudAnchor(ArSession *session,
+                                                   const char *cloud_anchor_id,
+                                                   ArAnchor **out_cloud_anchor);
+
+/// @}
+
+// === ArPose methods ===
+
+/// @addtogroup pose
+/// @{
+
+/// Allocates and initializes a new pose object.  @c pose_raw points to an array
+/// of 7 floats, describing the rotation (quaternion) and translation of the
+/// pose in the same order as the first 7 elements of the Android
+/// @c Sensor.TYPE_POSE_6DOF values documented on <a
+/// href="https://developer.android.com/reference/android/hardware/SensorEvent.html#values"
+/// >@c SensorEvent.values() </a>
+///
+/// The order of the values is: qx, qy, qz, qw, tx, ty, tz.
+///
+/// If @c pose_raw is null, initializes with the identity pose.
+void ArPose_create(const ArSession *session,
+                   const float *pose_raw,
+                   ArPose **out_pose);
+
+/// Releases memory used by a pose object.
+void ArPose_destroy(ArPose *pose);
+
+/// Extracts the quaternion rotation and translation from a pose object.
+/// @param[in]  session       The ARCore session
+/// @param[in]  pose          The pose to extract
+/// @param[out] out_pose_raw  Pointer to an array of 7 floats, to be filled with
+///     the quaternion rotation and translation as described in ArPose_create().
+void ArPose_getPoseRaw(const ArSession *session,
+                       const ArPose *pose,
+                       float *out_pose_raw);
+
+/// Converts a pose into a 4x4 transformation matrix.
+/// @param[in]  session                  The ARCore session
+/// @param[in]  pose                     The pose to convert
+/// @param[out] out_matrix_col_major_4x4 Pointer to an array of 16 floats, to be
+///     filled with a column-major homogenous transformation matrix, as used by
+///     OpenGL.
+void ArPose_getMatrix(const ArSession *session,
+                      const ArPose *pose,
+                      float *out_matrix_col_major_4x4);
+
+/// @}
+
+// === ArCamera methods ===
+
+/// @addtogroup camera
+/// @{
+
+/// Sets @c out_pose to the pose of the user's device in the world coordinate
+/// space at the time of capture of the current camera texture. The position and
+/// orientation of the pose follow the device's physical camera (they are not
+/// affected by display orientation) and uses OpenGL camera conventions (+X
+/// right, +Y up, -Z in the direction the camera is looking).
+///
+/// Note: This pose is only useful when ArCamera_getTrackingState() returns
+/// #AR_TRACKING_STATE_TRACKING and otherwise should not be used.
+///
+/// @param[in]    session  The ARCore session
+/// @param[in]    camera   The session's camera (retrieved from any frame).
+/// @param[inout] out_pose An already-allocated ArPose object into which the
+///     pose will be stored.
+void ArCamera_getPose(const ArSession *session,
+                      const ArCamera *camera,
+                      ArPose *out_pose);
+
+/// Sets @c out_pose to the pose of the user's device in the world coordinate
+/// space at the time of capture of the current camera texture. The position of
+/// the pose is located at the device's camera, while the orientation
+/// approximately matches the orientation of the display (considering display
+/// rotation), using OpenGL camera conventions (+X right, +Y up, -Z in the
+/// direction the camera is looking).
+///
+/// Note: This pose is only useful when ArCamera_getTrackingState() returns
+/// #AR_TRACKING_STATE_TRACKING and otherwise should not be used.
+///
+/// See also: ArCamera_getViewMatrix()
+///
+/// @param[in]    session  The ARCore session
+/// @param[in]    camera   The session's camera (retrieved from any frame).
+/// @param[inout] out_pose An already-allocated ArPose object into which the
+///     pose will be stored.
+void ArCamera_getDisplayOrientedPose(const ArSession *session,
+                                     const ArCamera *camera,
+                                     ArPose *out_pose);
+
+/// Returns the view matrix for the camera for this frame. This matrix performs
+/// the inverse transfrom as the pose provided by
+/// ArCamera_getDisplayOrientedPose().
+///
+/// @param[in]    session           The ARCore session
+/// @param[in]    camera            The session's camera.
+/// @param[inout] out_col_major_4x4 Pointer to an array of 16 floats, to be
+///     filled with a column-major homogenous transformation matrix, as used by
+///     OpenGL.
+void ArCamera_getViewMatrix(const ArSession *session,
+                            const ArCamera *camera,
+                            float *out_col_major_4x4);
+
+/// Gets the current state of the pose of this camera. If this state is anything
+/// other than #AR_TRACKING_STATE_TRACKING the Camera's pose should not be
+/// considered useful.
+void ArCamera_getTrackingState(const ArSession *session,
+                               const ArCamera *camera,
+                               ArTrackingState *out_tracking_state);
+
+/// Computes a projection matrix for rendering virtual content on top of the
+/// camera image. Note that the projection matrix reflects the current display
+/// geometry and display rotation.
+///
+/// @param[in]    session            The ARCore session
+/// @param[in]    camera             The session's camera.
+/// @param[in]    near               Specifies the near clip plane, in meters
+/// @param[in]    far                Specifies the far clip plane, in meters
+/// @param[inout] dest_col_major_4x4 Pointer to an array of 16 floats, to
+///     be filled with a column-major homogenous transformation matrix, as used
+///     by OpenGL.
+void ArCamera_getProjectionMatrix(const ArSession *session,
+                                  const ArCamera *camera,
+                                  float near,
+                                  float far,
+                                  float *dest_col_major_4x4);
+
+/// Releases a reference to the camera.  This must match a call to
+/// ArFrame_acquireCamera().
+///
+/// This method may safely be called with @c nullptr - it will do nothing.
+void ArCamera_release(ArCamera *camera);
+
+/// @}
+
+// === ArFrame methods ===
+
+/// @addtogroup frame
+/// @{
+
+/// Allocates a new ArFrame object, storing the pointer into @c *out_frame.
+///
+/// Note: the same ArFrame can be used repeatedly when calling ArSession_update.
+void ArFrame_create(const ArSession *session, ArFrame **out_frame);
+
+/// Releases an ArFrame and any references it holds.
+void ArFrame_destroy(ArFrame *frame);
+
+/// Checks if the display rotation or viewport geometry changed since the
+/// previous call to ArSession_update(). The application should re-query
+/// ArCamera_getProjectionMatrix() and ArFrame_transformDisplayUvCoords()
+/// whenever this emits non-zero.
+void ArFrame_getDisplayGeometryChanged(const ArSession *session,
+                                       const ArFrame *frame,
+                                       int32_t *out_geometry_changed);
+
+/// Returns the timestamp in nanoseconds when this image was captured. This can
+/// be used to detect dropped frames or measure the camera frame rate. The time
+/// base of this value is specifically <b>not</b> defined, but it is likely
+/// similar to <tt>clock_gettime(CLOCK_BOOTTIME)</tt>.
+void ArFrame_getTimestamp(const ArSession *session,
+                          const ArFrame *frame,
+                          int64_t *out_timestamp_ns);
+
+/// Transform the given texture coordinates to correctly show the background
+/// image. This will account for the display rotation, and any additional
+/// required adjustment. For performance, this function should be called only if
+/// ArFrame_hasDisplayGeometryChanged() emits true.
+///
+/// @param[in]    session      The ARCore session
+/// @param[in]    frame        The current frame.
+/// @param[in]    num_elements The number of floats to transform.  Must be
+///     a multiple of 2.  @c uvs_in and @c uvs_out must point to arrays of at
+///     least this many floats.
+/// @param[in]    uvs_in       Input UV coordinates in normalized screen space.
+/// @param[inout] uvs_out      Output UV coordinates in texture coordinates.
+void ArFrame_transformDisplayUvCoords(const ArSession *session,
+                                      const ArFrame *frame,
+                                      int32_t num_elements,
+                                      const float *uvs_in,
+                                      float *uvs_out);
+
+/// Performs a ray cast from the user's device in the direction of the given
+/// location in the camera view. Intersections with detected scene geometry are
+/// returned, sorted by distance from the device; the nearest intersection is
+/// returned first.
+///
+/// Note: Significant geometric leeway is given when returning hit results. For
+/// example, a plane hit may be generated if the ray came close, but did not
+/// actually hit within the plane extents or plane bounds
+/// (ArPlane_isPoseInExtents() and ArPlane_isPoseInPolygon() can be used to
+/// determine these cases). A point (point cloud) hit is generated when a point
+/// is roughly within one finger-width of the provided screen coordinates.
+///
+/// The resulting list is ordered by distance, with the nearest hit first
+///
+/// Note: If not tracking, the hit_result_list will be empty. <br>
+/// Note: If called on an old frame (not the latest produced by
+///     ArSession_update() the hit_result_list will be empty).
+///
+/// @param[in]    session         The ARCore session.
+/// @param[in]    frame           The current frame.
+/// @param[in]    pixel_x         Logical X position within the view, as from an
+///     Android UI event.
+/// @param[in]    pixel_y         Logical X position within the view.
+/// @param[inout] hit_result_list The list to fill.  This list must have been
+///     previously allocated using ArHitResultList_create().  If the list has
+///     been previously used, it will first be cleared.
+void ArFrame_hitTest(const ArSession *session,
+                     const ArFrame *frame,
+                     float pixel_x,
+                     float pixel_y,
+                     ArHitResultList *hit_result_list);
+
+/// Gets the current ambient light estimate, if light estimation was enabled.
+///
+/// @param[in]    session            The ARCore session.
+/// @param[in]    frame              The current frame.
+/// @param[inout] out_light_estimate The light estimate to fill.  This object
+///    must have been previously created with ArLightEstimate_create().
+void ArFrame_getLightEstimate(const ArSession *session,
+                              const ArFrame *frame,
+                              ArLightEstimate *out_light_estimate);
+
+/// Acquires the current set of estimated 3d points attached to real-world
+/// geometry. A matching call to PointCloud_release() must be made when the
+/// application is done accessing the point cloud.
+///
+/// Note: This information is for visualization and debugging purposes only. Its
+/// characteristics and format are subject to change in subsequent versions of
+/// the API.
+///
+/// @param[in]  session         The ARCore session.
+/// @param[in]  frame           The current frame.
+/// @param[out] out_point_cloud Pointer to an @c ArPointCloud* receive the
+///     address of the point cloud.
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from
+///   by ArSession_update().
+/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many point clouds are currently held.
+ArStatus ArFrame_acquirePointCloud(const ArSession *session,
+                                   const ArFrame *frame,
+                                   ArPointCloud **out_point_cloud);
+
+/// Returns the camera object for the session. Note that this Camera instance is
+/// long-lived so the same instance is returned regardless of the frame object
+/// this method was called on.
+void ArFrame_acquireCamera(const ArSession *session,
+                           const ArFrame *frame,
+                           ArCamera **out_camera);
+
+/// Gets the camera metadata for the current camera image.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_DEADLINE_EXCEEDED if @c frame is not the latest frame from
+///   by ArSession_update().
+/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many metadata objects are currently
+///   held.
+/// - #AR_ERROR_NOT_YET_AVAILABLE if the camera failed to produce metadata for
+///   the given frame. Note: this will commonly happen for few frames right
+///   after @c ArSession_resume() due to the camera stack bringup.
+ArStatus ArFrame_acquireImageMetadata(const ArSession *session,
+                                      const ArFrame *frame,
+                                      ArImageMetadata **out_metadata);
+
+/// Gets the set of anchors that were changed by the ArSession_update() that
+/// produced this Frame.
+///
+/// @param[in]    session            The ARCore session
+/// @param[in]    frame              The current frame.
+/// @param[inout] out_anchor_list The list to fill.  This list must have
+///     already been allocated with ArAnchorList_create().  If previously
+///     used, the list will first be cleared.
+void ArFrame_getUpdatedAnchors(const ArSession *session,
+                               const ArFrame *frame,
+                               ArAnchorList *out_anchor_list);
+
+/// Gets the set of trackables of a particular type that were changed by the
+/// ArSession_update() call that produced this Frame.
+///
+/// @param[in]    session            The ARCore session
+/// @param[in]    frame              The current frame.
+/// @param[in]    filter_type        The type(s) of trackables to return.  See
+///     ::ArTrackableType for legal values.
+/// @param[inout] out_trackable_list The list to fill.  This list must have
+///     already been allocated with ArTrackableList_create().  If previously
+///     used, the list will first be cleared.
+void ArFrame_getUpdatedTrackables(const ArSession *session,
+                                  const ArFrame *frame,
+                                  ArTrackableType filter_type,
+                                  ArTrackableList *out_trackable_list);
+/// @}
+
+// === ArPointCloud methods ===
+
+/// @addtogroup pointcloud
+/// @{
+
+/// Retrieves the number of points in the point cloud.
+///
+void ArPointCloud_getNumberOfPoints(const ArSession *session,
+                                    const ArPointCloud *point_cloud,
+                                    int32_t *out_number_of_points);
+
+/// Retrieves a pointer to the point cloud data.
+///
+/// Each point is represented by four consecutive values in the array; first the
+/// X, Y, Z position coordinates, followed by a confidence value. This is the
+/// same format as described in <a
+/// href="https://developer.android.com/reference/android/graphics/ImageFormat.html#DEPTH_POINT_CLOUD"
+/// >DEPTH_POINT_CLOUD</a>.
+///
+/// The pointer returned by this function is valid until ArPointCloud_release()
+/// is called. The application must copy the data if they wish to retain it for
+/// longer. The points are in world coordinates consistent with the frame it was
+/// obtained from. If the number of points is zero, then the value of
+/// @c *out_point_cloud_data should is undefined.
+void ArPointCloud_getData(const ArSession *session,
+                          const ArPointCloud *point_cloud,
+                          const float **out_point_cloud_data);
+
+/// Returns the timestamp in nanoseconds when this point cloud was observed.
+/// This timestamp uses the same time base as ArFrame_getTimestamp().
+void ArPointCloud_getTimestamp(const ArSession *session,
+                               const ArPointCloud *point_cloud,
+                               int64_t *out_timestamp_ns);
+
+/// Releases a reference to the point cloud.  This must match a call to
+/// ArFrame_acquirePointCloud().
+///
+/// This method may safely be called with @c nullptr - it will do nothing.
+void ArPointCloud_release(ArPointCloud *point_cloud);
+
+/// @}
+
+// === Image Metadata methods ===
+
+/// @addtogroup image
+/// @{
+
+/// Retrieves the capture metadata for the current camera image.
+///
+/// @c ACameraMetadata is a struct in Android NDK. Include NdkCameraMetadata.h
+/// to use this type.
+///
+/// Note: that the ACameraMetadata returned from this function will be invalid
+/// after its ArImageMetadata object is released.
+void ArImageMetadata_getNdkCameraMetadata(
+    const ArSession *session,
+    const ArImageMetadata *image_metadata,
+    const ACameraMetadata **out_ndk_metadata);
+
+/// Releases a reference to the metadata.  This must match a call to
+/// ArFrame_acquireImageMetadata().
+///
+/// This method may safely be called with @c nullptr - it will do nothing.
+void ArImageMetadata_release(ArImageMetadata *metadata);
+
+// === CPU Image Access types and methods ===
+/// Gets the image of the tracking camera relative to the input session and
+/// frame.
+/// Return values:
+/// @returns #AR_SUCCESS or any of:
+/// - #AR_ERROR_INVALID_ARGUMENT - one more input arguments are invalid.
+/// - #AR_ERROR_DEADLINE_EXCEEDED - the input frame is not the current frame.
+/// - #AR_ERROR_RESOURCE_EXHAUSTED - the caller app has exceeded maximum number
+///   of images that it can hold without releasing.
+/// - #AR_ERROR_NOT_YET_AVAILABLE - image with the timestamp of the input frame
+///   was not found within a bounded amount of time, or the camera failed to
+///   produce the image
+ArStatus ArFrame_acquireCameraImage(ArSession *session,
+                                    ArFrame *frame,
+                                    ArImage **out_image);
+
+/// Converts an ArImage object to an Android NDK AImage object.
+void ArImage_getNdkImage(const ArImage *image, const AImage **out_ndk_image);
+
+/// Releases an instance of ArImage returned by ArFrame_acquireCameraImage().
+void ArImage_release(ArImage *image);
+/// @}
+
+// === ArLightEstimate methods ===
+
+/// @addtogroup light
+/// @{
+
+/// Allocates a light estimate object.
+void ArLightEstimate_create(const ArSession *session,
+                            ArLightEstimate **out_light_estimate);
+
+/// Releases the provided light estimate object.
+void ArLightEstimate_destroy(ArLightEstimate *light_estimate);
+
+/// Retrieves the validity state of a light estimate.  If the resulting value of
+/// @c *out_light_estimate_state is not #AR_LIGHT_ESTIMATE_STATE_VALID, the
+/// estimate should not be used for rendering.
+void ArLightEstimate_getState(const ArSession *session,
+                              const ArLightEstimate *light_estimate,
+                              ArLightEstimateState *out_light_estimate_state);
+
+/// Retrieves the pixel intensity, in gamma space, of the current camera view.
+/// Values are in the range (0.0, 1.0), with zero being black and one being
+/// white.
+/// If rendering in gamma space, divide this value by 0.466, which is middle
+/// gray in gamma space, and multiply against the final calculated color after
+/// rendering.
+/// If rendering in linear space, first convert this value to linear space by
+/// rising to the power 2.2. Normalize the result by dividing it by 0.18 which
+/// is middle gray in linear space. Then multiply by the final calculated color
+/// after rendering.
+void ArLightEstimate_getPixelIntensity(const ArSession *session,
+                                       const ArLightEstimate *light_estimate,
+                                       float *out_pixel_intensity);
+
+/// Gets the color correction values that are uploaded to the fragment shader.
+/// Use the RGB scale factors (components 0-2) to match the color of the light
+/// in the scene. Use the pixel intensity (component 3) to match the intensity
+/// of the light in the scene.
+///
+/// `out_color_correction_4` components are:
+///   - `[0]` Red channel scale factor.
+///   - `[1]` Green channel scale factor.
+///   - `[2]` Blue channel scale factor.
+///   - `[3]` Pixel intensity. This is the same value as the one return from
+///       ArLightEstimate_getPixelIntensity().
+///
+///  The RGB scale factors can be used independently from the pixel intensity
+///  value. They are put together for the convenience of only having to upload
+///  one float4 to the fragment shader.
+///
+///  The RGB scale factors are not intended to brighten nor dim the scene.  They
+///  are only to shift the color of the virtual object towards the color of the
+///  light; not intensity of the light. The pixel intensity is used to match the
+///  intensity of the light in the scene.
+///
+///  Color correction values are reported in gamma space.
+///  If rendering in gamma space, component-wise multiply them against the final
+///  calculated color after rendering.
+///  If rendering in linear space, first convert the values to linear space by
+///  rising to the power 2.2. Then component-wise multiply against the final
+///  calculated color after rendering.
+void ArLightEstimate_getColorCorrection(const ArSession *session,
+                                        const ArLightEstimate *light_estimate,
+                                        float *out_color_correction_4);
+
+/// @}
+
+// === ArAnchorList methods ===
+
+/// @addtogroup anchor
+/// @{
+
+/// Creates an anchor list object.
+void ArAnchorList_create(const ArSession *session,
+                         ArAnchorList **out_anchor_list);
+
+/// Releases the memory used by an anchor list object, along with all the anchor
+/// references it holds.
+void ArAnchorList_destroy(ArAnchorList *anchor_list);
+
+/// Retrieves the number of anchors in this list.
+void ArAnchorList_getSize(const ArSession *session,
+                          const ArAnchorList *anchor_list,
+                          int32_t *out_size);
+
+/// Acquires a reference to an indexed entry in the list.  This call must
+/// eventually be matched with a call to ArAnchor_release().
+void ArAnchorList_acquireItem(const ArSession *session,
+                              const ArAnchorList *anchor_list,
+                              int32_t index,
+                              ArAnchor **out_anchor);
+
+// === ArAnchor methods ===
+
+/// Retrieves the pose of the anchor in the world coordinate space. This pose
+/// produced by this call may change each time ArSession_update() is called.
+/// This pose should only be used for rendering if ArAnchor_getTrackingState()
+/// returns #AR_TRACKING_STATE_TRACKING.
+///
+/// @param[in]    session  The ARCore session.
+/// @param[in]    anchor   The anchor to retrieve the pose of.
+/// @param[inout] out_pose An already-allocated ArPose object into which the
+///     pose will be stored.
+void ArAnchor_getPose(const ArSession *session,
+                      const ArAnchor *anchor,
+                      ArPose *out_pose);
+
+/// Retrieves the current state of the pose of this anchor.
+void ArAnchor_getTrackingState(const ArSession *session,
+                               const ArAnchor *anchor,
+                               ArTrackingState *out_tracking_state);
+
+/// Tells ARCore to stop tracking and forget this anchor.  This call does not
+/// release the reference to the anchor - that must be done separately using
+/// ArAnchor_release().
+void ArAnchor_detach(ArSession *session, ArAnchor *anchor);
+
+/// Releases a reference to an anchor. This does not mean that the anchor will
+/// stop tracking, as it will be obtainable from e.g. ArSession_getAllAnchors()
+/// if any other references exist.
+///
+/// This method may safely be called with @c nullptr - it will do nothing.
+void ArAnchor_release(ArAnchor *anchor);
+
+/// Acquires the cloud anchor ID of the anchor. The ID acquired is an ASCII
+/// null-terminated string. The acquired ID must be released after use by the
+/// @c ArString_release function. For anchors with cloud state
+/// #AR_CLOUD_ANCHOR_STATE_NONE or #AR_CLOUD_ANCHOR_STATE_TASK_IN_PROGRESS, this
+/// will always be an empty string.
+///
+/// @param[in]    session             The ARCore session.
+/// @param[in]    anchor              The anchor to retrieve the cloud ID of.
+/// @param[inout] out_cloud_anchor_id A pointer to the acquired ID string.
+void ArAnchor_acquireCloudAnchorId(ArSession *session,
+                                   ArAnchor *anchor,
+                                   char **out_cloud_anchor_id);
+
+/// Gets the current cloud anchor state of the anchor. This state is guaranteed
+/// not to change until update() is called.
+///
+/// @param[in]    session   The ARCore session.
+/// @param[in]    anchor    The anchor to retrieve the cloud state of.
+/// @param[inout] out_state The current cloud state of the anchor.
+void ArAnchor_getCloudAnchorState(const ArSession *session,
+                                  const ArAnchor *anchor,
+                                  ArCloudAnchorState *out_state);
+
+/// @}
+
+// === ArTrackableList methods ===
+
+/// @addtogroup trackable
+/// @{
+
+/// Creates a trackable list object.
+void ArTrackableList_create(const ArSession *session,
+                            ArTrackableList **out_trackable_list);
+
+/// Releases the memory used by a trackable list object, along with all the
+/// anchor references it holds.
+void ArTrackableList_destroy(ArTrackableList *trackable_list);
+
+/// Retrieves the number of trackables in this list.
+void ArTrackableList_getSize(const ArSession *session,
+                             const ArTrackableList *trackable_list,
+                             int32_t *out_size);
+
+/// Acquires a reference to an indexed entry in the list.  This call must
+/// eventually be matched with a call to ArTrackable_release().
+void ArTrackableList_acquireItem(const ArSession *session,
+                                 const ArTrackableList *trackable_list,
+                                 int32_t index,
+                                 ArTrackable **out_trackable);
+
+// === ArTrackable methods ===
+
+/// Releases a reference to a trackable. This does not mean that the trackable
+/// will necessarily stop tracking. The same trackable may still be included in
+/// from other calls, for example ArSession_getAllTrackables().
+///
+/// This method may safely be called with @c nullptr - it will do nothing.
+void ArTrackable_release(ArTrackable *trackable);
+
+/// Retrieves the type of the trackable.  See ::ArTrackableType for valid types.
+void ArTrackable_getType(const ArSession *session,
+                         const ArTrackable *trackable,
+                         ArTrackableType *out_trackable_type);
+
+/// Retrieves the current state of ARCore's knowledge of the pose of this
+/// trackable.
+void ArTrackable_getTrackingState(const ArSession *session,
+                                  const ArTrackable *trackable,
+                                  ArTrackingState *out_tracking_state);
+
+/// Creates an Anchor at the given pose in the world coordinate space, attached
+/// to this Trackable, and acquires a reference to it. The type of Trackable
+/// will determine the semantics of attachment and how the Anchor's pose will be
+/// updated to maintain this relationship. Note that the relative offset between
+/// the pose of multiple Anchors attached to a Trackable may adjust slightly
+/// over time as ARCore updates its model of the world.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_NOT_TRACKING if the trackable's tracking state was not
+///   #AR_TRACKING_STATE_TRACKING
+/// - #AR_ERROR_SESSION_PAUSED if the session was paused
+/// - #AR_ERROR_RESOURCE_EXHAUSTED if too many anchors exist
+ArStatus ArTrackable_acquireNewAnchor(ArSession *session,
+                                      ArTrackable *trackable,
+                                      ArPose *pose,
+                                      ArAnchor **out_anchor);
+
+/// Gets the set of anchors attached to this trackable.
+///
+/// @param[in]    session         The ARCore session
+/// @param[in]    trackable       The trackable to query the anchors of.
+/// @param[inout] out_anchor_list The list to fill.  This list must have
+///     already been allocated with ArAnchorList_create().  If previously
+///     used, the list will first be cleared.
+void ArTrackable_getAnchors(const ArSession *session,
+                            const ArTrackable *trackable,
+                            ArAnchorList *out_anchor_list);
+
+/// @}
+
+// === ArPlane methods ===
+
+/// @addtogroup plane
+/// @{
+
+/// Acquires a reference to the plane subsuming this plane.
+///
+/// Two or more planes may be automatically merged into a single parent plane,
+/// resulting in this method acquiring the parent plane when called with each
+/// child plane. A subsumed plane becomes identical to the parent plane, and
+/// will continue behaving as if it were independently tracked, for example
+/// being included in the output of ArFrame_getUpdatedTrackables().
+///
+/// In cases where a subsuming plane is itself subsumed, this function
+/// will always return the topmost non-subsumed plane.
+///
+/// Note: this function will set @c *out_subsumed_by to NULL if the plane is not
+/// subsumed.
+void ArPlane_acquireSubsumedBy(const ArSession *session,
+                               const ArPlane *plane,
+                               ArPlane **out_subsumed_by);
+
+/// Retrieves the type (orientation) of the plane.  See ::ArPlaneType.
+void ArPlane_getType(const ArSession *session,
+                     const ArPlane *plane,
+                     ArPlaneType *out_plane_type);
+
+/// Returns the pose of the center of the detected plane. The pose's transformed
+/// +Y axis will be point normal out of the plane, with the +X and +Z axes
+/// orienting the extents of the bounding rectangle.
+///
+/// @param[in]    session  The ARCore session.
+/// @param[in]    plane    The plane for which to retrieve center pose.
+/// @param[inout] out_pose An already-allocated ArPose object into which the
+///     pose will be stored.
+void ArPlane_getCenterPose(const ArSession *session,
+                           const ArPlane *plane,
+                           ArPose *out_pose);
+
+/// Retrieves the length of this plane's bounding rectangle measured along the
+/// local X-axis of the coordinate space defined by the output of
+/// ArPlane_getCenterPose().
+void ArPlane_getExtentX(const ArSession *session,
+                        const ArPlane *plane,
+                        float *out_extent_x);
+
+/// Retrieves the length of this plane's bounding rectangle measured along the
+/// local Z-axis of the coordinate space defined by the output of
+/// ArPlane_getCenterPose().
+void ArPlane_getExtentZ(const ArSession *session,
+                        const ArPlane *plane,
+                        float *out_extent_z);
+
+/// Retrieves the number of elements (not vertices) in the boundary polygon.
+/// The number of vertices is 1/2 this size.
+void ArPlane_getPolygonSize(const ArSession *session,
+                            const ArPlane *plane,
+                            int32_t *out_polygon_size);
+
+/// Returns the 2D vertices of a convex polygon approximating the detected
+/// plane, in the form <tt>[x1, z1, x2, z2, ...]</tt>. These X-Z values are in
+/// the plane's local x-z plane (y=0) and must be transformed by the pose
+/// (ArPlane_getCenterPose()) to get the boundary in world coordinates.
+///
+/// @param[in]    session        The ARCore session.
+/// @param[in]    plane          The plane to retrieve the polygon from.
+/// @param[inout] out_polygon_xz A pointer to an array of floats.  The length of
+///     this array must be at least that reported by ArPlane_getPolygonSize().
+void ArPlane_getPolygon(const ArSession *session,
+                        const ArPlane *plane,
+                        float *out_polygon_xz);
+
+/// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained
+/// from a HitResult) is in the plane's rectangular extents.
+void ArPlane_isPoseInExtents(const ArSession *session,
+                             const ArPlane *plane,
+                             const ArPose *pose,
+                             int32_t *out_pose_in_extents);
+
+/// Sets @c *out_pose_in_extents to non-zero if the given pose (usually obtained
+/// from a HitResult) is in the plane's polygon.
+void ArPlane_isPoseInPolygon(const ArSession *session,
+                             const ArPlane *plane,
+                             const ArPose *pose,
+                             int32_t *out_pose_in_polygon);
+
+/// @}
+
+// === ArPoint methods ===
+
+/// @addtogroup point
+/// @{
+
+/// Returns the pose of the point.
+/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL, the
+/// orientation will follow the behavior described in ArHitResult_getHitPose().
+/// If ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then
+/// returns an orientation that is identity or close to identity.
+/// @param[in]    session  The ARCore session.
+/// @param[in]    point    The point to retrieve the pose of.
+/// @param[inout] out_pose An already-allocated ArPose object into which the
+/// pose will be stored.
+void ArPoint_getPose(const ArSession *session,
+                     const ArPoint *point,
+                     ArPose *out_pose);
+
+/// Returns the OrientationMode of the point. For @c Point objects created by
+/// ArFrame_hitTest().
+/// If OrientationMode is ESTIMATED_SURFACE_NORMAL, then normal of the surface
+/// centered around the ArPoint was estimated succesfully.
+///
+/// @param[in]    session              The ARCore session.
+/// @param[in]    point                The point to retrieve the pose of.
+/// @param[inout] out_orientation_mode OrientationMode output result for the
+///     the point.
+void ArPoint_getOrientationMode(const ArSession *session,
+                                const ArPoint *point,
+                                ArPointOrientationMode *out_orientation_mode);
+
+/// @}
+
+// === ArAugmentedImage methods ===
+
+/// @addtogroup augmented_image
+/// @{
+
+/// Returns the pose of the center of the detected image. The pose's
+/// transformed +Y axis will be point normal out of the image.
+///
+/// If the tracking state is PAUSED/STOPPED, this returns the pose when the
+/// image state was last TRACKING, or the identity pose if the image state has
+/// never been TRACKING.
+void ArAugmentedImage_getCenterPose(const ArSession *session,
+                                    const ArAugmentedImage *augmented_image,
+                                    ArPose *out_pose);
+
+/// Retrieves the estimated width, in metres, of the corresponding physical
+/// image, as measured along the local X-axis of the coordinate space with
+/// origin and axes as defined by ArAugmentedImage_getCenterPose().
+///
+/// ARCore will attempt to estimate the physical image's width and continuously
+/// update this estimate based on its understanding of the world. If the
+/// optional physical size is specified in the image database, this estimation
+/// process will happen more quickly. However, the estimated size may be
+/// different from the originally specified size.
+///
+/// If the tracking state is PAUSED/STOPPED, this returns the estimated width
+/// when the image state was last TRACKING. If the image state has never been
+/// TRACKING, this returns 0, even the image has a specified physical size in
+/// the image database.
+void ArAugmentedImage_getExtentX(const ArSession *session,
+                                 const ArAugmentedImage *augmented_image,
+                                 float *out_extent_x);
+
+/// Retrieves the estimated height, in metres, of the corresponding physical
+/// image, as measured along the local Z-axis of the coordinate space with
+/// origin and axes as defined by ArAugmentedImage_getCenterPose().
+///
+/// ARCore will attempt to estimate the physical image's height and continuously
+/// update this estimate based on its understanding of the world. If an optional
+/// physical size is specified in the image database, this estimation process
+/// will happen more quickly. However, the estimated size may be different from
+/// the originally specified size.
+///
+/// If the tracking state is PAUSED/STOPPED, this returns the estimated height
+/// when the image state was last TRACKING. If the image state has never been
+/// TRACKING, this returns 0, even the image has a specified physical size in
+/// the image database.
+void ArAugmentedImage_getExtentZ(const ArSession *session,
+                                 const ArAugmentedImage *augmented_image,
+                                 float *out_extent_z);
+
+/// Returns the zero-based positional index of this image from its originating
+/// image database.
+///
+/// This index serves as the unique identifier for the image in the database.
+void ArAugmentedImage_getIndex(const ArSession *session,
+                               const ArAugmentedImage *augmented_image,
+                               int32_t *out_index);
+
+/// Returns the name of this image.
+///
+/// The image name is not guaranteed to be unique.
+///
+/// This function will allocate memory for the name string, and set
+/// *out_augmented_image_name to point to that string. The caller must release
+/// the string using ArString_release when the string is no longer needed.
+void ArAugmentedImage_acquireName(const ArSession *session,
+                                  const ArAugmentedImage *augmented_image,
+                                  char **out_augmented_image_name);
+
+/// @}
+
+// === ArAugmentedImageDatabase methods ===
+
+/// @addtogroup augmented_image_database
+/// @{
+
+/// Creates a new empty image database.
+void ArAugmentedImageDatabase_create(
+    const ArSession *session,
+    ArAugmentedImageDatabase **out_augmented_image_database);
+
+/// Creates a new image database from a byte array. The contents of the byte
+/// array must have been generated by the command-line database generation tool
+/// provided in the SDK, or at runtime from ArAugmentedImageDatabase_serialize.
+///
+/// Note: this function takes about 10-20ms for a 5MB byte array. Run this in a
+/// background thread if this affects your application.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_DATA_INVALID_FORMAT - the bytes are in an invalid format.
+/// - #AR_ERROR_DATA_UNSUPPORTED_VERSION - the database is not supported by
+///   this version of the SDK.
+ArStatus ArAugmentedImageDatabase_deserialize(
+    const ArSession *session,
+    const uint8_t *database_raw_bytes,
+    int64_t database_raw_bytes_size,
+    ArAugmentedImageDatabase **out_augmented_image_database);
+
+/// Serializes an image database to a byte array.
+///
+/// This function will allocate memory for the serialized raw byte array, and
+/// set *out_image_database_raw_bytes to point to that byte array. The caller is
+/// expected to release the byte array using ArByteArray_release when the byte
+/// array is no longer needed.
+void ArAugmentedImageDatabase_serialize(
+    const ArSession *session,
+    const ArAugmentedImageDatabase *augmented_image_database,
+    uint8_t **out_image_database_raw_bytes,
+    int64_t *out_image_database_raw_bytes_size);
+
+/// Adds a single named image of unknown physical size to an image database,
+/// from an array of grayscale pixel values. Returns the zero-based positional
+/// index of the image within the image database.
+///
+/// If the physical size of the image is known, use
+/// ArAugmentedImageDatabase_addImageWithPhysicalSize instead, to improve image
+/// detection time.
+///
+/// For images added via ArAugmentedImageDatabase_addImage, ARCore estimates the
+/// physical image's size and pose at runtime when the physical image is visible
+/// and is being tracked. This extra estimation step will require the user to
+/// move their device to view the physical image from different viewpoints
+/// before the size and pose of the physical image can be estimated.
+///
+/// This function takes time to perform non-trivial image processing (20ms -
+/// 30ms), and should be run on a background thread.
+///
+/// The image name is expected to be a null-terminated string in UTF-8 format.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_IMAGE_INSUFFICIENT_QUALITY - image quality is insufficient, e.g.
+///   because of lack of features in the image.
+ArStatus ArAugmentedImageDatabase_addImage(
+    const ArSession *session,
+    ArAugmentedImageDatabase *augmented_image_database,
+    const char *image_name,
+    const uint8_t *image_grayscale_pixels,
+    int32_t image_width_in_pixels,
+    int32_t image_height_in_pixels,
+    int32_t image_stride_in_pixels,
+    int32_t *out_index);
+
+/// Adds a single named image to an image database, from an array of grayscale
+/// pixel values, along with a positive physical width in meters for this image.
+/// Returns the zero-based positional index of the image within the image
+/// database.
+///
+/// If the physical size of the image is not known, use
+/// ArAugmentedImageDatabase_addImage instead, at the expense of an increased
+/// image detection time.
+///
+/// For images added via ArAugmentedImageDatabase_addImageWithPhysicalSize,
+/// ARCore can estimate the pose of the physical image at runtime as soon as
+/// ARCore detects the physical image, without requiring the user to move the
+/// device to view the physical image from different viewpoints. Note that
+/// ARCore will refine the estimated size and pose of the physical image as it
+/// is viewed from different viewpoints.
+///
+/// This function takes time to perform non-trivial image processing (20ms -
+/// 30ms), and should be run on a background thread.
+///
+/// The image name is expected to be a null-terminated string in UTF-8 format.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_IMAGE_INSUFFICIENT_QUALITY - image quality is insufficient, e.g.
+///   because of lack of features in the image.
+/// - #AR_ERROR_INVALID_ARGUMENT - image_width_in_meters is <= 0.
+ArStatus ArAugmentedImageDatabase_addImageWithPhysicalSize(
+    const ArSession *session,
+    ArAugmentedImageDatabase *augmented_image_database,
+    const char *image_name,
+    const uint8_t *image_grayscale_pixels,
+    int32_t image_width_in_pixels,
+    int32_t image_height_in_pixels,
+    int32_t image_stride_in_pixels,
+    float image_width_in_meters,
+    int32_t *out_index);
+
+/// Returns the number of images in the image database.
+void ArAugmentedImageDatabase_getNumImages(
+    const ArSession *session,
+    const ArAugmentedImageDatabase *augmented_image_database,
+    int32_t *out_num_images);
+
+/// Releases memory used by an image database.
+void ArAugmentedImageDatabase_destroy(
+    ArAugmentedImageDatabase *augmented_image_database);
+
+/// @}
+
+// === ArHitResultList methods ===
+
+/// @addtogroup hit
+/// @{
+
+/// Creates a hit result list object.
+void ArHitResultList_create(const ArSession *session,
+                            ArHitResultList **out_hit_result_list);
+
+/// Releases the memory used by a hit result list object, along with all the
+/// trackable references it holds.
+void ArHitResultList_destroy(ArHitResultList *hit_result_list);
+
+/// Retrieves the number of hit results in this list.
+void ArHitResultList_getSize(const ArSession *session,
+                             const ArHitResultList *hit_result_list,
+                             int32_t *out_size);
+
+/// Copies an indexed entry in the list.  This acquires a reference to any
+/// trackable referenced by the item, and releases any reference currently held
+/// by the provided result object.
+///
+/// @param[in]    session           The ARCore session.
+/// @param[in]    hit_result_list   The list from which to copy an item.
+/// @param[in]    index             Index of the entry to copy.
+/// @param[inout] out_hit_result    An already-allocated ArHitResult object into
+///     which the result will be copied.
+void ArHitResultList_getItem(const ArSession *session,
+                             const ArHitResultList *hit_result_list,
+                             int32_t index,
+                             ArHitResult *out_hit_result);
+
+// === ArHitResult methods ===
+
+/// Allocates an empty hit result object.
+void ArHitResult_create(const ArSession *session, ArHitResult **out_hit_result);
+
+/// Releases the memory used by a hit result object, along with any
+/// trackable reference it holds.
+void ArHitResult_destroy(ArHitResult *hit_result);
+
+/// Returns the distance from the camera to the hit location, in meters.
+void ArHitResult_getDistance(const ArSession *session,
+                             const ArHitResult *hit_result,
+                             float *out_distance);
+
+/// Returns the pose of the intersection between a ray and detected real-world
+/// geometry. The position is the location in space where the ray intersected
+/// the geometry. The orientation is a best effort to face the user's device,
+/// and its exact definition differs depending on the Trackable that was hit.
+///
+/// ::ArPlane : X+ is perpendicular to the cast ray and parallel to the plane,
+/// Y+ points along the plane normal (up, for #AR_PLANE_HORIZONTAL_UPWARD_FACING
+/// planes), and Z+ is parallel to the plane, pointing roughly toward the
+/// user's device.
+///
+/// ::ArPoint :
+/// Attempt to estimate the normal of the surface centered around the hit test.
+/// Surface normal estimation is most likely to succeed on textured surfaces
+/// and with camera motion.
+/// If ArPoint_getOrientationMode() returns ESTIMATED_SURFACE_NORMAL,
+/// then X+ is perpendicular to the cast ray and parallel to the physical
+/// surface centered around the hit test, Y+ points along the estimated surface
+/// normal, and Z+ points roughly toward the user's device. If
+/// ArPoint_getOrientationMode() returns INITIALIZED_TO_IDENTITY, then X+ is
+/// perpendicular to the cast ray and points right from the perspective of the
+/// user's device, Y+ points up, and Z+ points roughly toward the user's device.
+///
+/// If you wish to retain the location of this pose beyond the duration of a
+/// single frame, create an anchor using ArHitResult_acquireNewAnchor() to save
+/// the pose in a physically consistent way.
+///
+/// @param[in]    session    The ARCore session.
+/// @param[in]    hit_result The hit result to retrieve the pose of.
+/// @param[inout] out_pose   An already-allocated ArPose object into which the
+///     pose will be stored.
+void ArHitResult_getHitPose(const ArSession *session,
+                            const ArHitResult *hit_result,
+                            ArPose *out_pose);
+
+/// Acquires reference to the hit trackable. This call must be paired with a
+/// call to ArTrackable_release().
+void ArHitResult_acquireTrackable(const ArSession *session,
+                                  const ArHitResult *hit_result,
+                                  ArTrackable **out_trackable);
+
+/// Creates a new anchor at the hit location. See ArHitResult_getHitPose() for
+/// details.  This is equivalent to creating an anchor on the hit trackable at
+/// the hit pose.
+///
+/// @return #AR_SUCCESS or any of:
+/// - #AR_ERROR_NOT_TRACKING
+/// - #AR_ERROR_SESSION_PAUSED
+/// - #AR_ERROR_RESOURCE_EXHAUSTED
+/// - #AR_ERROR_DEADLINE_EXCEEDED - hit result must be used before the next call
+///     to update().
+ArStatus ArHitResult_acquireNewAnchor(ArSession *session,
+                                      ArHitResult *hit_result,
+                                      ArAnchor **out_anchor);
+
+/// @}
+
+// Utility methods for releasing data.
+
+/// Releases a string acquired using an ARCore API function.
+///
+/// @param[in] str The string to be released.
+void ArString_release(char *str);
+
+/// Releases a byte array created using an ARCore API function.
+void ArByteArray_release(uint8_t *byte_array);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // ARCORE_C_API_H_
diff --git a/src/utils/Sk3D.h b/src/utils/Sk3D.h
deleted file mode 100644
index 46bb5cc..0000000
--- a/src/utils/Sk3D.h
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef Sk3D_DEFINED
-#define Sk3D_DEFINED
-
-#include "SkPoint3.h"
-#include "SkMatrix44.h"
-
-void Sk3LookAt(SkMatrix44* dst, const SkPoint3& eye, const SkPoint3& center, const SkPoint3& up);
-bool Sk3Perspective(SkMatrix44* dst, float near, float far, float angle);
-void Sk3MapPts(SkPoint dst[], const SkMatrix44& m4, const SkPoint3 src[], int count);
-
-#endif
-