Xamarin NuGet Package >
Geo Augmented Markers for Android

This tutorial will guide you through the creation of a simple geolocalized AR application that show geolocalized markers through the device camera when you look in the direction they are located in the world or drawn on a map. This tutorial is largely similar to our Local Planar Marker tutorial with a few important modifications. Our SDK is designed for use with Visual Studio (versions 2015+), and supports Android SDK level 15+ (21+ for the 64 bit version). Android NDK is not required.

First create a new application in Visual Studio using the Blank App (Android) template

Write your App name and choose project location in the Create New Project Wizard and click Ok.

When the project is ready close the GettinStarted.Xamarin page and open the MainActivity that xamarin had created. Now it's time to set-up your app for development with Pikkart's AR SDK.
Open the Properties menu and in the Android Manifest tab, if you have already purchased a license, set the Package name you provided when buying your Pikkart's AR license, otherwise use "com.pikkart.trial" if you're using the trial license provided by default when you register as a developer.

 Now right click on the voice References of the project and click Manage NuGet Packages...

In the NuGet page type Pikkart.ArSdk in the search bar to find our sdk
Select the Pikkart.ArSdk package and in the detail page click on Install.

After the package has been added you should see Com.Pikkart.AR.Geo and Com.Pikkart.AR.Recognition assemblies on the References menu.
Now install the Xamarin.Android.Support.v4 v25.1.0+ and the Xamarin.GooglePlayServices.Maps packages v42.1001.0+ too.




Then add the following permissions in the Android Manifest tab in the project properties:
- ACCESS_NETWORK_STATE
- CAMERA
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
- ACCESS_COARSE_LOCATION
- ACCESS_FINE_LOCATION
and in the Manifest.xml file add the following permissions:


<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

The activity holding Pikkart's AR stuff must have set ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation and an AppCompat theme in the MainActivity.cs class declaration as in the following example:

[Activity(Label = "Pikkart_SDK_tutorial", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, Theme = "@style/AppTheme")]
public class MainActivity : Activity
{ 
    ...
}

it is recommended to add the Style.xml file in the Resources folder and add your own theme in this file. Add a resources node to Styles.xml and define a style node with the name of your custom theme. For example, here is a Styles.xml file that defines AppTheme:

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"></style>
</resources>

Finally you have to retrieve a valid API Key from https://developers.google.com in order to use the Google Map services and add this line to the manifest inside the <application> tag:

<meta-data android:name="com.google.android.geo.API_KEY" 
android:value="<Put here your API Key>"/>

In order to support Android 6.0+ we have to slightly modify the MainActivity.cs code in order to ask the user for permissions about location access, camera access and read/write access to the SD memory.

Create a private function InitLayout() and move OnCreate's SetContentView there.

private void InitLayout() {
    SetContentView(Resource.Layout.Main);
}

Now add a new method that will ask the user for permission. The following method will ask the user for CameraWriteExternalStorage, ReadExternalStorage, AccessNetworkState, AccessCoarseLocation and AccessFineLocation permissions. The method uses ActivityCompat.CheckSelfPermission to check if requested permission have been granted before, if not it uses ActivityCompat.RequestPermissions to requests missing permissions. The method receive a integer unique code to identify the request. Create a class member variable (i.e. called m_permissionCode) and assign a unique integer value to it.

private void CheckPermissions(int code)
{
    string[] permissions_required = new string[] {
        Android.Manifest.Permission.Camera,
        Android.Manifest.Permission.WriteExternalStorage,
        Android.Manifest.Permission.ReadExternalStorage,
        Android.Manifest.Permission.AccessNetworkState,
        Android.Manifest.Permission.AccessCoarseLocation,
        Android.Manifest.Permission.AccessFineLocation
    };
    List<string> permissions_not_granted_list = new List<string>();
    foreach (string permission in permissions_required)
    {
        if (ActivityCompat.CheckSelfPermission(ApplicationContext, permission) != Permission.Granted)
        {
            permissions_not_granted_list.Add(permission);
        }
    }
    if (permissions_not_granted_list.Count > 0)
    {
        string[] permissions = new string[permissions_not_granted_list.Count];
        permissions = permissions_not_granted_list.ToArray();
        ActivityCompat.RequestPermissions(this, permissions, code);
    }
    else
    {
        InitLayout();
    }
}

Now we have to implement a callback method that will be called by the OS once the user has granted or dismissed the requested permissions. This method implementation cycle through all requested permissions and check that all of them have been granted:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
    if (requestCode == m_permissionCode)
    {
        bool ok = true;
        for (int i = 0; i < grantResults.Length; ++i)
        {
            ok = ok && (grantResults[i] == Permission.Granted);
        }
        if (ok)
        {
            InitLayout();
        }
        else
        {
            Toast.MakeText(this, "Error: required permissions not granted!", ToastLength.Short).Show();
            Finish();
        }
    }
}

We can then modify our OnCreate method this way:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    //if not Android 6+ run the app
    if (Build.VERSION.SdkInt < BuildVersionCodes.M)
    {
        InitLayout();
    }
    else
    {
        CheckPermissions(m_permissionCode);
    }
}

Your main activity should look like this:

using Android.App;
using Android.Widget;
using Android.OS;
using Android.Support.V7.App;
using System.Collections.Generic;
using Android.Support.V4.App;
using Android.Content.PM;
using Android.Runtime;

namespace Pikkart_SDK_tutorial
{
    [Activity(Label = "Pikkart_SDK_tutorial", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : AppCompatActivity
    {
        private const int m_permissionCode = 1234;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            //if not Android 6+ run the app
            if (Build.VERSION.SdkInt < BuildVersionCodes.M)
            {
                InitLayout();
            }
            else
            {
                CheckPermissions(m_permissionCode);
            }
        }

        private void InitLayout()
        {
            SetContentView(Resource.Layout.Main);
        }

        private void CheckPermissions(int code)
        {
            string[] permissions_required = new string[] {
                Android.Manifest.Permission.Camera,
                Android.Manifest.Permission.WriteExternalStorage,
                Android.Manifest.Permission.ReadExternalStorage,
                Android.Manifest.Permission.AccessNetworkState,
                Android.Manifest.Permission.AccessCoarseLocation,
                Android.Manifest.Permission.AccessFineLocation
            };

            List permissions_not_granted_list = new List();
            foreach (string permission in permissions_required)
            {
                if (ActivityCompat.CheckSelfPermission(ApplicationContext, permission) != Permission.Granted)
                {
                    permissions_not_granted_list.Add(permission);
                }
            }
            if (permissions_not_granted_list.Count > 0)
            {
                string[] permissions = new string[permissions_not_granted_list.Count];
                permissions = permissions_not_granted_list.ToArray();
                ActivityCompat.RequestPermissions(this, permissions, code);
            }
            else
            {
                InitLayout();
            }
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            if (requestCode == m_permissionCode)
            {
                bool ok = true;
                for (int i = 0; i < grantResults.Length; ++i)
                {
                    ok = ok && (grantResults[i] == Permission.Granted);
                }
                if (ok)
                {
                    InitLayout();
                }
                else
                {
                    Toast.MakeText(this, "Error: required permissions not granted!", ToastLength.Short).Show();
                    Finish();
                }
            }
        }
    }
}

Now it's time to add and enable Pikkart SDK's GEO AR functionalities.

Add Pikkart's AR GeoFragment to your AR activity modifyng the Main.axml file
 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/ar_main_layout"
    tools:context="pikkart.com.pikkartartutorial.MainActivity">
  <fragment 
      android:layout_width="match_parent" 
      android:layout_height="match_parent"
      android:id="@+id/geo_fragment" 
      android:name="com.pikkart.ar.geo.GeoFragment" />
</RelativeLayout>

Now it's time to start the geo augmentation process and add some geo locations. We can do it inside our InitLayout() by adding:


GeoFragment m_geoFragment = ((GeoFragment)FragmentManager.FindFragmentById(Resource.Id.geo_fragment));
m_geoFragment.DisableRecognition();
m_geoFragment.SetGeoListener(this);

Location loc1 = new Location("loc1");
loc1.Latitude = 44.654894;
loc1.Longitude = 10.914749;

Location loc2 = new Location("loc2");
loc2.Latitude = 44.653505;
loc2.Longitude = 10.909653;

Location loc3 = new Location("loc3");
loc3.Latitude = 44.647315;
loc3.Longitude = 10.924802;

List<GeoElement> geoElementList = new List<GeoElement>();
geoElementList.Add(new GeoElement(loc1, "1", "COOP, Modena"));
geoElementList.Add(new GeoElement(loc2, "2", "Burger King, Modena"));
geoElementList.Add(new GeoElement(loc3, "3", "Piazza Matteotti, Modena"));

m_geoFragment.SetGeoElements(geoElementList);

First we recover our GeoFragment and disable standard AR recognition with DisableRecognition() and leave GeoAR and GeoMap enabled. We set our MainActivity class as listener of a few callbacks from our geolocation system explained later on in this tutorial. We then add three sample locations around our current location: first create a Location object that hold geo coordinates of our points of interests amd then create a GeoElement object that hold the Location, and ID tag and a textual name of the POI. Remember to change longitude and latitude to places nearby you, the ones provided in this tutorial are locations around our headquarter in Italy. 
Finally add the GeoElements to our geolocalization system with the SetGeoElements(...) method. 

Our MainActivity class needs to implement our geo localization specialized interface IARGeoListener. First modify the MainClass declaration:

public class MainActivity : AppCompatActivity, IArGeoListener
{
    ....
}

Then we have to implement the following callback functions:

  • This function is called every time a GeoElement has been clicked by a user, either on the augmented view or on the map.
    public void OnGeoElementClicked(GeoElement p0)
    {
    }
  • This function is called when the user has clicked on the empty spaces in the camera or map.
    public void OnMapOrCameraClicked()
    {
    }
  • This function is called when the user geo location changes. You can use this callback to request new GeoElements from a web service and add them to our GeoFragment.
    public void OnGeolocationChanged(Location p0)
    {
    }
  • This function is called soon after thestartup process and inform the app that the GeoFragment has moved its Views on top of the current viewport. You can use this helper callback to then move on top of the GeoFragment your own Views.
    public void OnGeoBringInterfaceOnTop()
    {
    }

You can run the application now. The app currently doesn't show anything, it just sample sensor data and does its magic in the background. It's time to add some visuals to it. In order to do that we need to create an OpenGL view, set it up and render the camera view and additional augmented content. We have created some helper classes to help you set up the rendering process that are included in the sample package. To use them copy the c# classes contained inside the zip into the project root folder (the folder where your MainActivity is). You will find three new c# classes in your app project as in the following image:

Change the package name in the three classes to match the package name of your MainActivity.

namespace TutorialApp
namespace Pikkart_SDK_tutorial

GLTextureView is a widget class that combines an Android TextureView and an OpenGL context, managing its setup, rendering etc. RenderUtils contains various OpenGL helper functions (Shader compilation etc.) and some Matrix operations. The Mesh class manage a 3D mesh (loading from a json file and rendering).

In order to create our own 3D renderer we have to extend our GLTextureView class and create a modified version that will perform our custom rendering. We use a structure that is fairly common in the world of 3D rendering on Android: the GLTextureView class manages Android UI related stuff, the set up of an OpenGL rendering context and a few other things. For the actual rendering it defines an interface (GLTextureView.Renderer) with a few callback functions that will be called during the various phases of the OpenGL context set-up, the android view set-up and the rendering cycle. Before creating our own derived version of GLTextureView we will define our rendering class implementing the GLTextureView.Renderer interface.
This class implements 4 callback functions:

  • public void OnSurfaceCreated(IGL10 gl, EGLConfig config) that is called every time the OpenGL rendering surface is created or recreated. When this is called all OpenGL related stuff (buffers, VBOs, textures etc.) must be recreated as well.
  • public void onSurfaceChanged(IGL10 gl, int width, int height) is called every time the OpenGL surface is changed, without destroying it.
  • public void onSurfaceDestroyed() this is called when the OpenGL surface is destroyed. Usually after this point you can clean and delete your objects.
  • public void onDrawFrame(IGL10 gl) this is called on every rendering cycle. This is were the actual rendering of OpenGL related stuff happens.

In our implementation we have added a couple of support function such as public bool computeModelViewProjectionMatrix(float[] mvpMatrix) that computes the model-view-projection matrix that will be used by the OpenGL renderer starting from the projection and marker attitude/position matrices obtained from Pikkart's AR SDK.

We make use of an important static function of the GeoFragment class:

  • public static void RecognitionFragment.RenderCamera(int viewportWidth, int viewportHeight, int angle)

The RenderCamera function render the last processed camera image, it's the function that enables the app to render the actual camera view, this function must be called inside your OpenGL context rendering cycle, usually one of the first functions called inside the public void onDrawFrame(GL10 gl) function of our Renderer class implementation.

Create a new c# class in your Android project (in the same folder as the MainActivity), name it ARRenderer. The following is the full implementation of our Renderer class implementing the GLTextureView.Renderer interface (copy and paste the following code inside the ARRenderer class and remember to set the correct package name).
 



using Android.Content;
using Javax.Microedition.Khronos.Egl;
using Javax.Microedition.Khronos.Opengles;
using Com.Pikkart.AR.Geo;
using Com.Pikkart.AR.Recognition;

namespace Pikkart_SDK_tutorial
{
    public class ARRenderer : GLTextureView.Renderer
    {
        public bool IsActive = false;
        //the rendering viewport dimensions
        private int ViewportWidth;
        private int ViewportHeight;
        //normalized screen orientation (0=landscale, 90=portrait, 180=inverse landscale, 270=inverse portrait)
        private int Angle;
        //
        private Context context;

        /* Constructor. */
        public ARRenderer(Context con)
        {
            context = con;
        }

        /** Called to draw the current frame. */
        public void onDrawFrame(IGL10 gl)
        {
            if (!IsActive) return;

            gl.GlClear(GL10.GlColorBufferBit | GL10.GlDepthBufferBit);

            // Call our native function to render camera content
            GeoFragment.RenderCamera(ViewportWidth, ViewportHeight, Angle);

            gl.GlFinish();
        }

        /** Called when the surface changed size. */
        public void onSurfaceChanged(IGL10 gl, int width, int height)
        {

        }

        /** Called when the surface is created or recreated.
         * Reinitialize OpenGL related stuff here*/
        public void OnSurfaceCreated(IGL10 gl, EGLConfig config)
        {
            gl.GlClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        }

        /** Called when the surface is destroyed. */
        public void onSurfaceDestroyed()
        {

        }

        /* this will be called by our GLTextureView-derived class to update screen sizes and orientation */
        public void UpdateViewport(int viewportWidth, int viewportHeight, int angle)
        {
            ViewportWidth = viewportWidth;
            ViewportHeight = viewportHeight;
            Angle = angle;
            GeoNativeWrapper.UpdateProjectionCamera(ARNativeWrapper.CameraWidth(), ARNativeWrapper.CameraHeight(), ViewportWidth, ViewportHeight, Angle);
        }
    }
}

Create a new c# class in your Android project (in the same folder as the MainActivity), name it ARView.  

The implementation of our OpenGL AR view class, derived from our GLTextureView class, is more straightforward (copy and paste the following code inside the ARView class and remember to set the correct namespace).



using System;
using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Util;
using Javax.Microedition.Khronos.Egl;

namespace Pikkart_SDK_tutorial
{
    public class ARView : GLTextureView
    {
        private Context _context;
        //our renderer implementation
        private ARRenderer _renderer;

        /* Called when device configuration has changed */
        protected override void OnConfigurationChanged(Configuration newConfig)
        {
            //here we force our layout to fill the parent
            if (Parent is FrameLayout)
            {
                LayoutParameters = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MatchParent,
                        FrameLayout.LayoutParams.MatchParent, GravityFlags.Center);
            }
            else if (Parent is RelativeLayout)
            {
                LayoutParameters = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MatchParent,
                        RelativeLayout.LayoutParams.MatchParent);
            }
        }

        /* Called when layout is created or modified (i.e. because of device rotation changes etc.) */
        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if (!changed) return;
            int angle = 0;
            //here we compute a normalized orientation independent of the device class (tablet or phone)
            //so that an angle of 0 is always landscape, 90 always portrait etc.
            var windowmanager = _context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
            Display display = windowmanager.DefaultDisplay;
            int rotation = (int)display.Rotation;
            if (Resources.Configuration.Orientation == Android.Content.Res.Orientation.Landscape)
            {
                switch (rotation)
                {
                    case 0:
                        angle = 0;
                        break;
                    case 1:
                        angle = 0;
                        break;
                    case 2:
                        angle = 180;
                        break;
                    case 3:
                        angle = 180;
                        break;
                    default:
                        break;
                }
            }
            else
            {
                switch (rotation)
                {
                    case 0:
                        angle = 90;
                        break;
                    case 1:
                        angle = 270;
                        break;
                    case 2:
                        angle = 270;
                        break;
                    case 3:
                        angle = 90;
                        break;
                    default:
                        break;
                }
            }

            int realWidth;
            int realHeight;
            if ((int)Build.VERSION.SdkInt >= 17)
            {
                //new pleasant way to get real metrics
                DisplayMetrics realMetrics = new DisplayMetrics();
                display.GetRealMetrics(realMetrics);
                realWidth = realMetrics.WidthPixels;
                realHeight = realMetrics.HeightPixels;

            }
            else if ((int)Build.VERSION.SdkInt >= 14)
            {
                //reflection for this weird in-between time
                try
                {
                    Java.Lang.Reflect.Method mGetRawH = Display.Class.GetMethod("getRawHeight");
                    var mGetRawW = Display.Class.GetMethod("getRawWidth");
                    realWidth = (int)mGetRawW.Invoke(display);
                    realHeight = (int)mGetRawH.Invoke(display);
                }
                catch (Exception e)
                {
                    //this may not be 100% accurate, but it's all we've got
                    realWidth = display.Width;
                    realHeight = display.Height;
                }
            }
            else
            {
                //This should be close, as lower API devices should not have window navigation bars
                realWidth = display.Width;
                realHeight = display.Height;
            }
            _renderer.UpdateViewport(right - left, bottom - top, angle);
        }

        /* Constructor. */
        public ARView(Context context) : base(context)
        {
            _context = context;
            init();
            _renderer = new ARRenderer(this._context);
            setRenderer(_renderer);
            ((ARRenderer)_renderer).IsActive = true;
            SetOpaque(true);
        }

        /* Initialization. */
        public void init()
        {
            setEGLContextFactory(new ContextFactory());
            setEGLConfigChooser(new ConfigChooser(8, 8, 8, 0, 16, 0));
        }

        /* Checks the OpenGL error.*/
        private static void checkEglError(String prompt, IEGL10 egl)
        {
            int error;
            while ((error = egl.EglGetError()) != EGL10.EglSuccess)
            {
                Log.Error("PikkartCore3", String.Format("%s: EGL error: 0x%x", prompt, error));
            }
        }

        /* A private class that manages the creation of OpenGL contexts. Pretty standard stuff*/
        private class ContextFactory : EGLContextFactory
        {
            private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;

            public EGLContext createContext(IEGL10 egl, EGLDisplay display, EGLConfig eglConfig)
            {
                EGLContext context;
                //Log.i("PikkartCore3","Creating OpenGL ES 2.0 context");
                checkEglError("Before eglCreateContext", egl);
                int[] attrib_list_gl20 = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EglNone };
                context = egl.EglCreateContext(display, eglConfig, EGL10.EglNoContext, attrib_list_gl20);
                checkEglError("After eglCreateContext", egl);
                return context;
            }

            public void destroyContext(IEGL10 egl, EGLDisplay display, EGLContext context)
            {
                egl.EglDestroyContext(display, context);
            }
        }


        /* A private class that manages the the config chooser. Pretty standard stuff */
        private class ConfigChooser : EGLConfigChooser
        {
            public ConfigChooser(int r, int g, int b, int a, int depth, int stencil)
            {
                mRedSize = r;
                mGreenSize = g;
                mBlueSize = b;
                mAlphaSize = a;
                mDepthSize = depth;
                mStencilSize = stencil;
            }

            private EGLConfig getMatchingConfig(IEGL10 egl, EGLDisplay display, int[] configAttribs)
            {
                // Get the number of minimally matching EGL configurations
                int[] num_config = new int[1];
                egl.EglChooseConfig(display, configAttribs, null, 0, num_config);
                int numConfigs = num_config[0];
                if (numConfigs <= 0)
                    throw new Exception("No matching EGL configs");
                // Allocate then read the array of minimally matching EGL configs
                EGLConfig[] configs = new EGLConfig[numConfigs];
                egl.EglChooseConfig(display, configAttribs, configs, numConfigs, num_config);
                // Now return the "best" one
                return chooseConfig(egl, display, configs);
            }

            public EGLConfig chooseConfig(IEGL10 egl, EGLDisplay display)
            {
                // This EGL config specification is used to specify 2.0 com.pikkart.ar.rendering. We use a minimum size of 4 bits for
                // red/green/blue, but will perform actual matching in chooseConfig() below.
                int EGL_OPENGL_ES2_BIT = 0x0004;
                int[] s_configAttribs_gl20 = {EGL10.EglRedSize, 4, EGL10.EglGreenSize, 4, EGL10.EglBlueSize, 4,
                    EGL10.EglRenderableType, EGL_OPENGL_ES2_BIT, EGL10.EglNone};
                return getMatchingConfig(egl, display, s_configAttribs_gl20);
            }

            public EGLConfig chooseConfig(IEGL10 egl, EGLDisplay display, EGLConfig[] configs)
            {
                bool bFoundDepth = false;
                foreach (EGLConfig config in configs)
                {
                    int d = findConfigAttrib(egl, display, config, EGL10.EglDepthSize, 0);
                    if (d == mDepthSize) bFoundDepth = true;
                }
                if (bFoundDepth == false) mDepthSize = 16; //min value
                foreach (EGLConfig config in configs)
                {
                    int d = findConfigAttrib(egl, display, config, EGL10.EglDepthSize, 0);
                    int s = findConfigAttrib(egl, display, config, EGL10.EglStencilSize, 0);
                    // We need at least mDepthSize and mStencilSize bits
                    if (d < mDepthSize || s < mStencilSize)
                        continue;
                    // We want an *exact* match for red/green/blue/alpha
                    int r = findConfigAttrib(egl, display, config, EGL10.EglRedSize, 0);
                    int g = findConfigAttrib(egl, display, config, EGL10.EglGreenSize, 0);
                    int b = findConfigAttrib(egl, display, config, EGL10.EglBlueSize, 0);
                    int a = findConfigAttrib(egl, display, config, EGL10.EglAlphaSize, 0);

                    if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
                        return config;
                }

                return null;
            }

            private int findConfigAttrib(IEGL10 egl, EGLDisplay display, EGLConfig config, int attribute, int defaultValue)
            {
                if (egl.EglGetConfigAttrib(display, config, attribute, mValue))
                    return mValue[0];
                return defaultValue;
            }

            // Subclasses can adjust these values:
            protected int mRedSize;
            protected int mGreenSize;
            protected int mBlueSize;
            protected int mAlphaSize;
            protected int mDepthSize;
            protected int mStencilSize;
            private int[] mValue = new int[1];
        }
    }
}

We are almost set. Now we need to add our ARView class to our app on top of Pikkart's AR RecognitionFragment. Simply modify the InitLayout function of the MainActivity class as:

private void InitLayout() {
    ...
    ...

    RelativeLayout rl = (RelativeLayout)FindViewById(Resource.Id.ar_main_layout);
    m_arView = new ARView(this);
    rl.AddView(m_arView, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MatchParent, FrameLayout.LayoutParams.MatchParent));

    m_geoFragment.SetCameraTextureView(m_arView);

    ...
    ...
}

Its also good practive to forward at least the OnResume and OnPause callback to our new ARView object:

    protected override void OnResume()
    {
        base.OnResume();
        if (m_arView != null) m_arView.onResume();
    }

    protected override void OnPause()
    {
        base.OnPause();
        //pause our renderer and associated videos
        if (m_arView != null) m_arView.onPause();
    }

You can now run your app on a real device. 

Marker view customization

Starting from Pikkart Android SDK 2.6.0, user can customize marker view used either in geolocazionation and maps using MarkerViewAdapter class. This class has following methods and attribute to override:

-View MarkerView { get; }

-GetView(GeoElement p0)

-GetSelectedView(GeoElement p0)

To use MarkerViewAdapter you have to create a local class that extends it. User can customize the marker icon overriding the GetView (and the GetSelectedView) method. To change the icon of the marker you have to override the GetView() method, retrieve the ImageView with id “image” and setting your image, same for the selected view. We provide you a set of colored markers in the sample package  to add to the drawable folder.

A possible implementation is:

private class MyMarkerViewAdapter : MarkerViewAdapter
{
    Context _context;
    public MyMarkerViewAdapter(Context context, int width, int height) : base(context, width, height)
    {
        IsDefaultMarker = true;
        _context = context;
    }

    public override View GetView(GeoElement p0)
    {
        ImageView imageView = (ImageView)MarkerView.FindViewById(Resource.Id.image);
        imageView.SetImageResource(Resource.Drawable.map_marker_yellow);
        imageView.Invalidate();
        return MarkerView;
    }

    public override View GetSelectedView(GeoElement p0)
    {
        ImageView imageView = (ImageView)MarkerView.FindViewById(Resource.Id.image);
        imageView.SetImageResource(Resource.Drawable.map_marker_dark_green);
        imageView.Invalidate();
        return MarkerView;
    }
}

Then you can set the new MarkerViewAdapter calling the SetMarkerViewAdapters method of the GeoFragment in the InitLayout like this:

private void InitLayout()
{
    ...
    m_geoFragment.SetMarkerViewAdapters(new MyMarkerViewAdapter(this, 51, 73), new MyMarkerViewAdapter(this, 30, 43));
}

A complete Android Studio project can be found on Github @  https://github.com/pikkart-support/Pikkart-AR-SDK-GeoAugmentedMarker-Xamarin.Android-Sample (remember to add you license file in the assets folder!).