Tutorial: Instruções passo a passo para criar um novo aplicativo Android usando Âncoras Espaciais do AzureTutorial: Step-by-step instructions to create a new Android app using Azure Spatial Anchors

Este tutorial mostra como criar um novo aplicativo Android que se integra à funcionalidade de ARCore com Âncoras Espaciais do Azure.This tutorial will show you how to create a new Android app that integrates ARCore functionality with Azure Spatial Anchors.

Pré-requisitosPrerequisites

Para concluir este tutorial, verifique se você tem:To complete this tutorial, make sure you have:

IntroduçãoGetting started

Inicie o Android Studio.Start Android Studio. Na janela Bem-vindo ao Android Studio, clique em Iniciar um novo projeto do Android Studio.In the Welcome to Android Studio window, click Start a new Android Studio project. Ou, se você já tiver um projeto aberto, selecione Arquivo->Novo Projeto.Or, if you have a project already opened, select File->New Project.

Na janela Criar Novo Projeto, na seção Telefone e Tablet, escolha Atividade Vazia e clique em Avançar.In the Create New Project window, under the Phone and Tablet section, choose Empty Activity, and click Next. Em seguida, em Nível mínimo da API, escolha API 26: Android 8.0 (Oreo) e verifique se a Linguagem de programação está definida como Java.Then, under Minimum API level, choose API 26: Android 8.0 (Oreo), and ensure the Language is set to Java. Talvez você queira alterar a localização e o nome do projeto, bem como o nome do pacote.You may want to change the Project Name & Location, and the Package name. Deixe as outras opções como estão.Leave the other options as they are. Clique em Concluir.Click Finish. O Instalador do Componente será executado.The Component Installer will run. Quando terminar, clique em Concluir.Once it's done, click Finish. Depois de algum processamento, o Android Studio abrirá o IDE.After some processing, Android Studio will open the IDE.

Experimentá-loTrying it out

Para testar seu novo aplicativo, conecte o dispositivo de desenvolvedor habilitado para seu computador de desenvolvimento com um cabo USB.To test out your new app, connect your developer-enabled device to your development machine with a USB cable. Clique em Executar->Executar 'aplicativo' .Click Run->Run 'app'. Na janela Selecione o Destino de Implantação, selecione seu dispositivo e clique em OK.In the Select Deployment Target window, select your device, and click OK. Android Studio instala o aplicativo no dispositivo conectado e o inicia.Android Studio installs the app on your connected device and starts it. Agora você deve ver "Olá, Mundo!"You should now see "Hello World!" exibido no aplicativo em execução no seu dispositivo.displayed in the app running on your device. Clique em Executar->Interromper 'aplicativo' .Click Run->Stop 'app'.

Integração do ARCoreIntegrating ARCore

O ARCore é a plataforma do Google para a criação de experiências de realidade aumentada, permitindo que seu dispositivo controle sua própria posição conforme ele se move e cria seu próprio entendimento do mundo real.ARCore is Google's platform for building Augmented Reality experiences, enabling your device to track its position as it moves and builds its own understanding of the real world.

Modifique app\manifests\AndroidManifest.xml para incluir as entradas a seguir dentro do nó <manifest> da raiz.Modify app\manifests\AndroidManifest.xml to include the following entries inside the root <manifest> node. Este snippet de código faz algumas coisas:This code snippet does a few things:

  • Ele permitirá que seu aplicativo acesse sua câmera do dispositivo.It will allow your app to access your device camera.
  • Ele também garantirá que seu aplicativo só seja visível no Google Play Store para dispositivos que deem suporte a ARCore.It will also ensure your app is only visible in the Google Play Store to devices that support ARCore.
  • Ele também configurará o Google Play Store para baixar e instalar o ARCore, se ainda não estiver instalado, no ato da instalação do aplicativo.It will configure the Google Play Store to download and install ARCore, if it isn't installed already, when your app is installed.
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" />

<application>
    ...
    <meta-data android:name="com.google.ar.core" android:value="required" />
    ...
</application>

Modifique Gradle Scripts\build.gradle (Module: app) para incluir a entrada a seguir.Modify Gradle Scripts\build.gradle (Module: app) to include the following entry. Esse código assegurará que o aplicativo tenha o ARCore versão 1.8 como destino.This code will ensure that your app targets ARCore version 1.8. Após essa alteração, você poderá receber uma notificação do Gradle solicitando que você sincronize: clique em Sincronizar agora.After this change, you might get a notification from Gradle asking you to sync: click Sync now.

dependencies {
    ...
    implementation 'com.google.ar:core:1.11.0'
    ...
}

Integração do SceneformIntegrating Sceneform

O Sceneform simplifica a renderização de cenas 3D realistas em aplicativos de realidade aumentada, sem precisar aprender OpenGL.Sceneform makes it simple to render realistic 3D scenes in Augmented Reality apps, without having to learn OpenGL.

Modifique Gradle Scripts\build.gradle (Module: app) para incluir as entradas a seguir.Modify Gradle Scripts\build.gradle (Module: app) to include the following entries. Este código permitirá que seu aplicativo use constructos de linguagem do Java 8, exigidos pelo Sceneform.This code will allow your app to use language constructs from Java 8, which Sceneform requires. Também garantirá que aplicativo se destine ao Sceneform versão 1.8, já que ele deve corresponder à versão do ARCore que o aplicativo está usando.It will also ensure your app targets Sceneform version 1.8, since it should match the version of ARCore your app is using. Após essa alteração, você poderá receber uma notificação do Gradle solicitando que você sincronize: clique em Sincronizar agora.After this change, you might get a notification from Gradle asking you to sync: click Sync now.

android {
    ...

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.11.0'
    ...
}

Abra seu app\res\layout\activity_main.xml e substitua o elemento <TextView> de Olá Mundo existente com o seguinte ArFragment.Open your app\res\layout\activity_main.xml, and replace the existing Hello Wolrd <TextView> element with the following ArFragment. Esse código fará com que o feed da câmera seja exibido na tela, permitindo que o ARCore acompanhe a posição do dispositivo conforme ele se move.This code will cause the camera feed to be displayed on your screen enabling ARCore to track your device position as it moves.

<fragment android:name="com.google.ar.sceneform.ux.ArFragment"
    android:id="@+id/ux_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Reimplante seu aplicativo no dispositivo para validá-lo mais uma vez.Redeploy your app to your device to validate it once more. Dessa vez, deve ser solicitado que você forneça permissões de câmera.This time, you should be asked for camera permissions. Após a aprovação, você deverá ver a renderização do feed da câmera na tela.Once approved, you should see your camera feed rendering on your screen.

Colocar um objeto no mundo realPlace an object in the real world

Vamos criar e dispor um objeto usando o aplicativo.Let's create & place an object using your app. Primeiro, adicione as seguintes importações ao seu app\java\<PackageName>\MainActivity:First, add the following imports into your app\java\<PackageName>\MainActivity:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;

Em seguida, adicione as seguintes variáveis de membro na classe MainActivity:Then, add the following member variables into your MainActivity class:


public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();
    private ArFragment arFragment;

Depois, adicione o código a seguir ao método app\java\<PackageName>\MainActivity onCreate().Next, add the following code into your app\java\<PackageName>\MainActivity onCreate() method. Esse código conectará um ouvinte chamado handleTap(), que detectará quando o usuário toca a tela em seu dispositivo.This code will hook up a listener, called handleTap(), that will detect when the user taps the screen on your device. Se o toque ocorrer em uma superfície do mundo real que já tiver sido reconhecida pelo acompanhamento do ARCore, o ouvinte será executado.If the tap happens to be on a real world surface that has already been recognized by ARCore's tracking, the listener will run.

private ExecutorService executorService = Executors.newSingleThreadExecutor();

// <onCreate>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    });

Por fim, adicione o método handleTap() a seguir, que juntará tudo.Finally, add the following handleTap() method, that will tie everything together. Ele criará uma esfera e a colocará na localização tocada.It will create a sphere, and place it on the tapped location. A esfera inicialmente será preta, já que this.recommendedSessionProgress está definido como zero no momento.The sphere will initially be black, since this.recommendedSessionProgress is set to zero right now. Esse valor será ajustado posteriormente.This value will be adjusted later on.

// </initializeSession>

// <handleTap>
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        return;
    }
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
                            });
                    });

Reimplante seu aplicativo no dispositivo para validá-lo mais uma vez.Redeploy your app to your device to validate it once more. Neste momento, você pode mover em torno de seu dispositivo para fazer com que o ARCore comece a reconhecer o seu ambiente.This time, you can move around your device to get ARCore to start recognizing your environment. Em seguida, toque na tela para criar e colocar seu círculo preto sobre a superfície de sua escolha.Then, tap the screen to create & place your black sphere over the surface of your choice.

Anexar uma âncora espacial do Azure localAttach a local Azure Spatial Anchor

Modifique Gradle Scripts\build.gradle (Module: app) para incluir a entrada a seguir.Modify Gradle Scripts\build.gradle (Module: app) to include the following entry. Esse código assegurará que o aplicativo se destine às Âncoras Espaciais do Azure versão 1.3.0.This code will ensure that your app targets Azure Spatial Anchors version 1.3.0. Dito isso, fazer referência a qualquer versão recente das Âncoras Espaciais do Azure deve funcionar.That said, referencing any recent version of Azure Spatial Anchors should work.

dependencies {
    ...
    implementation "com.microsoft.azure.spatialanchors:spatialanchors_jni:[1.3.0]"
    implementation "com.microsoft.azure.spatialanchors:spatialanchors_java:[1.3.0]"
    ...
}

Clique com o botão direito do mouse em app\java\<PackageName>->Nova->Classe Java.Right-click app\java\<PackageName>->New->Java Class. Defina o Nome como MyFirstApp e a Superclasse como android.app.Application.Set Name to MyFirstApp, and Superclass to android.app.Application. Deixe as outras opções como estão.Leave the other options as they are. Clique em OK.Click OK. Um arquivo chamado MyFirstApp.java será criado.A file called MyFirstApp.java will be created. Adicione a importação a seguir a ele:Add the following import to it:

import com.microsoft.CloudServices;

Depois, adicione o código a seguir dentro da nova classe MyFirstApp, o que assegura que as Âncoras Espaciais do Azure sejam inicializadas com o contexto do aplicativo.Then, add the following code inside the new MyFirstApp class, which will ensure Azure Spatial Anchors is initialized with your application's context.

    @Override
    public void onCreate() {
        super.onCreate();
        CloudServices.initialize(this);
    }

Agora, modifique app\manifests\AndroidManifest.xml para incluir a entrada a seguir dentro do nó <application> da raiz.Now, modify app\manifests\AndroidManifest.xml to include the following entry inside the root <application> node. Esse código se conectará à classe Aplicativo que você criou em seu aplicativo.This code will hook up the Application class you created into your app.

    <application
        android:name=".MyFirstApp"
        ...
    </application>

De volta em app\java\<PackageName>\MainActivity, adicione as seguintes importações a ele:Back in app\java\<PackageName>\MainActivity, add the following imports into it:

import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;

import android.view.MotionEvent;
import android.util.Log;

import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.Scene;

Em seguida, adicione as seguintes variáveis de membro na classe MainActivity:Then, add the following member variables into your MainActivity class:

private ArFragment arFragment;
private AnchorNode anchorNode;
private Renderable nodeRenderable = null;
private float recommendedSessionProgress = 0f;

Em seguida, adicionaremos o método initializeSession() a seguir dentro da classe mainActivity.Next, let's add the following initializeSession() method inside your mainActivity class. Quando chamado, ele garantirá que uma sessão das Âncoras Espaciais do Azure seja criada e inicializada corretamente durante a inicialização do aplicativo.Once called, it will ensure an Azure Spatial Anchors session is created and properly initialized during the startup of your app.

// </onCreate>

// <initializeSession>
private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);

Agora, vamos conectar o método initializeSession() ao método onCreate().Now, let's hook your initializeSession() method into your onCreate() method. Além disso, nós garantiremos que quadros do feed da câmera sejam enviados ao SDK das Âncoras Espaciais do Azure para processamento.Also, we'll ensure that frames from your camera feed are sent to Azure Spatial Anchors SDK for processing.

private ExecutorService executorService = Executors.newSingleThreadExecutor();

// <onCreate>
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
    this.arFragment.setOnTapArPlaneListener(this::handleTap);

    this.sceneView = arFragment.getArSceneView();
    Scene scene = sceneView.getScene();
    scene.addOnUpdateListener(frameTime -> {
        if (this.cloudSession != null) {
            this.cloudSession.processFrame(sceneView.getArFrame());
        }
    });

Por fim, adicione o código a seguir ao método handleTap().Finally, add the following code into your handleTap() method. Ele anexará uma âncora espacial do Azure local à esfera preta que estamos colocando no mundo real.It will attach a local Azure Spatial Anchor to the black sphere that we're placing in the real world.

// </initializeSession>

// <handleTap>
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        return;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
                            });
                    });

Reimplante o aplicativo mais uma vez.Redeploy your app once more. Mova seu dispositivo, toque na tela e posicione uma esfera preta.Move around your device, tap the screen, and place a black sphere. Neste momento, no entanto, seu código criará e anexará uma âncora espacial do Azure local à esfera.This time, though, your code will be creating and attaching a local Azure Spatial Anchor to your sphere.

Antes de Avançar, você precisará criar um identificador e chave de conta das Âncoras Espaciais do Azure, se ainda não os tiver.Before proceeding any further, you'll need to create an Azure Spatial Anchors account Identifier and Key, if you don't already have them. Siga a seção a seguir para obtê-los.Follow the following section to obtain them.

Criar um recurso Âncoras EspaciaisCreate a Spatial Anchors resource

Vá para o Portal do Azure.Go to the Azure portal.

No painel de navegação esquerdo do portal do Azure, clique em Criar um recurso.In the left navigation pane in the Azure portal, select Create a resource.

Use a caixa de pesquisa para Âncoras Espaciais.Use the search box to search for Spatial Anchors.

Pesquisar Âncoras Espaciais

Selecione Âncoras Espaciais.Select Spatial Anchors. Na caixa de diálogo, selecione Criar.In the dialog box, select Create.

No caixa de diálogo Conta de Âncoras Espaciais:In the Spatial Anchors Account dialog box:

  • Insira um nome de recurso exclusivos, usando caracteres alfanuméricos regulares.Enter a unique resource name, using regular alphanumeric characters.

  • Selecione a assinatura que você deseja anexar o recurso.Select the subscription that you want to attach the resource to.

  • Crie um grupo de recursos, selecionando Criar novo.Create a resource group by selecting Create new. Denomine-o como myResourceGroup e selecione OK.Name it myResourceGroup and select OK. Um grupo de recursos é um contêiner lógico no qual os recursos do Azure, como aplicativos Web, bancos de dados e contas de armazenamento, são implantados e gerenciados.A resource group is a logical container into which Azure resources like web apps, databases, and storage accounts are deployed and managed. Por exemplo, é possível excluir posteriormente todo o grupo de recursos com uma única etapa simples.For example, you can choose to delete the entire resource group in one simple step later.

  • Selecione um local (região) para criar o recurso.Select a location (region) in which to place the resource.

  • Selecione Novo para começar a criar o recurso.Select New to begin creating the resource.

    Criar um recurso

Depois que o recurso é criado, o portal do Azure mostra que a implantação foi concluída.After the resource is created, Azure Portal will show that your deployment is complete. Clique em Ir para o recurso.Click Go to resource.

Implantação concluída

Em seguida, você pode exibir as propriedades do recurso.Then, you can view the resource properties. Copiar o valor de ID da conta em um editor de texto, pois você precisará dele mais tarde.Copy the resource's Account ID value into a text editor because you'll need it later.

Propriedades de recurso

Em Configurações, selecione Chave.Under Settings, select Key. Cópia do valor de Chave primária em um editor de texto.Copy the Primary key value into a text editor. Esse valor é o Account Key.This value is the Account Key. Você precisará dela mais tarde.You'll need it later.

Chave de conta

Carregar sua âncora local na nuvemUpload your local anchor into the cloud

Depois que tiver seu identificador e chave de conta das Âncoras Espaciais do Azure, podemos voltar para app\java\<PackageName>\MainActivity. Adicione as seguintes importações a ele:Once you have your Azure Spatial Anchors account Identifier and Key, we can go back in app\java\<PackageName>\MainActivity, add the following imports into it:

import com.google.ar.sceneform.Scene;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

import java.util.concurrent.CompletableFuture;

Em seguida, adicione as seguintes variáveis de membro na classe MainActivity:Then, add the following member variables into your MainActivity class:

private float recommendedSessionProgress = 0f;

private ArSceneView sceneView;
private CloudSpatialAnchorSession cloudSession;

private String anchorId = null;

Agora, adicione o código a seguir ao método initializeSession().Now, add the following code into your initializeSession() method. Primeiro, este código permitirá que seu aplicativo monitore o progresso que o SDK das Âncoras Espaciais do Azure faz conforme ele coleta quadros do feed da câmera.First, this code will allow your app to monitor the progress that the Azure Spatial Anchors SDK makes as it collects frames from your camera feed. Conforme ele o faz, a cor de sua esfera começará a se alterar de seu preto original para cinza.As it does, the color of your sphere will start changing from its original black, into grey. Em seguida, ela se tornará branca depois que quadros suficientes tiverem sido coletados para enviar sua âncora para a nuvem.Then, it will turn white once enough frames are collected to submit your anchor to the cloud. Em segundo lugar, esse código fornecerá as credenciais necessárias para se comunicar com o back-end de nuvem.Second, this code will provide the credentials needed to communicate with the cloud back-end. É aqui que você configurará seu aplicativo para usar seu identificador e chave de conta.Here is where you'll configure your app to use your account Identifier and Key. Você os copiou em um editor de texto ao configurar o recurso Âncoras Espaciais.You copied them into a text editor when setting up the Spatial Anchors resource.

// </onCreate>

// <initializeSession>
private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload)
            {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                    .thenAccept(material -> {
                        this.nodeRenderable.setMaterial(material);
                    });
            });
        }
    });

    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);

Em seguida, adicione o método uploadCloudAnchorAsync() a seguir dentro da classe mainActivity.Next, add the following uploadCloudAnchorAsync() method inside your mainActivity class. Quando chamado, esse método aguardará assincronamente até que quadros suficientes tenham sido coletados do seu dispositivo.Once called, this method will asynchronously wait until enough frames are collected from your device. Assim que isso acontecer, ele mudará a cor de sua esfera para amarelo e, em seguida, iniciará o upload de sua âncora espacial do Azure local para a nuvem.As soon as that happens, it will switch the color of your sphere to yellow, and then it will start uploading your local Azure Spatial Anchor into the cloud. Depois que o upload for concluído, o código retornará um identificador da âncora.Once the upload finishes, the code will return an anchor identifier.

private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
    synchronized (this.syncSessionProgress) {
        this.scanningForUpload = true;
    }

    return CompletableFuture.runAsync(() -> {
        try {
            float currentSessionProgress;
            do {
                synchronized (this.syncSessionProgress) {
                    currentSessionProgress = this.recommendedSessionProgress;
                }
                if (currentSessionProgress < 1.0) {
                    Thread.sleep(500);
                }
            }
            while (currentSessionProgress < 1.0);

            synchronized (this.syncSessionProgress) {
                this.scanningForUpload = false;
            }
            runOnUiThread(() -> {
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                    .thenAccept(yellowMaterial -> {
                        this.nodeRenderable.setMaterial(yellowMaterial);
                    });
            });

            this.cloudSession.createAnchorAsync(anchor).get();
        } catch (InterruptedException | ExecutionException e) {
            Log.e("ASAError", e.toString());
            throw new RuntimeException(e);
        }
    }, executorService).thenApply(ignore -> anchor.getIdentifier());
}

Por fim, vamos interligar tudo.Finally, let's hook everything together. Em seu método handleTap(), adicione o código a seguir.In your handleTap() method, add the following code. Ele invocará o método uploadCloudAnchorAsync() assim que a esfera for criada.It will invoke your uploadCloudAnchorAsync() method as soon as your sphere is created. Depois que o método retornar, o código a seguir executará uma atualização final à esfera, alterando sua cor para azul.Once the method returns, the code below will perform one final update to your sphere, changing its color to blue.

// </initializeSession>

// <handleTap>
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        return;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
            this.anchorNode.setRenderable(nodeRenderable);
            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

            uploadCloudAnchorAsync(cloudAnchor)
                .thenAccept(id -> {
                    this.anchorId = id;
                    Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                    runOnUiThread(() -> {
                        MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                    });

Reimplante o aplicativo mais uma vez.Redeploy your app once more. Mova seu dispositivo, toque na tela e posicione a esfera.Move around your device, tap the screen, and place your sphere. Neste momento, no entanto, a cor da esfera mudará de preto para branco, conforme os quadros da câmera forem coletados.This time, though, your sphere will change its color from black towards white, as camera frames are collected. Assim que tivermos quadros suficientes, a esfera ficará amarela e o upload para a nuvem começará.Once we have enough frames, the sphere will turn into yellow, and the cloud upload will start. Depois que o upload for concluído, a esfera ficará azul.Once the upload finishes, your sphere will turn blue. Opcionalmente, você também pode usar a janela Logcat dentro do Android Studio para monitorar as mensagens de log que o aplicativo está enviando.Optionally, you could also use the Logcat window inside Android Studio to monitor the log messages your app is sending. Por exemplo, o progresso da sessão durante capturas de quadro, bem como o identificador de âncora que a nuvem retorna uma vez que o upload é concluído.For example, the session progress during frame captures, and the anchor identifier that the cloud returns once the upload is completed.

Localizar a âncora espacial de nuvemLocate your cloud spatial anchor

Após a âncora ser carregada para a nuvem, estamos prontos para tentar localizá-la novamente.One your anchor is uploaded to the cloud, we're ready to attempt locating it again. Primeiro, vamos adicionar as importações a seguir ao seu código.First, let's add the following imports into your code.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

Depois, vamos adicionar o código a seguir ao método handleTap().Then, let's add the following code into your handleTap() method. Esse código vai:This code will:

  • Remover nossa esfera azul existente da tela.Remove our existing blue sphere from the screen.
  • Inicializar nossa sessão de Âncoras Espaciais do Azure novamente.Initialize our Azure Spatial Anchors session again. Esta ação assegurará que a âncora que vamos localizar venha da nuvem em vez de provir da âncora de local que criamos.This action will ensure that the anchor we're going to locate comes from the cloud instead of the local anchor we created.
  • Emita uma consulta para a âncora que é carregada para a nuvem.Issue a query for the anchor we uploaded to the cloud.
protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
    synchronized (this.syncTaps) {
        if (this.tapExecuted) {
            return;
        }

        this.tapExecuted = true;
    }

    if (this.anchorId != null) {
        this.anchorNode.getAnchor().detach();
        this.anchorNode.setParent(null);
        this.anchorNode = null;
        initializeSession();
        AnchorLocateCriteria criteria = new AnchorLocateCriteria();
        criteria.setIdentifiers(new String[]{this.anchorId});
        cloudSession.createWatcher(criteria);
        return;
    }

    this.anchorNode = new AnchorNode();
    this.anchorNode.setAnchor(hitResult.createAnchor());
    CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
    cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

    MaterialFactory.makeOpaqueWithColor(this, new Color(
            this.recommendedSessionProgress,
            this.recommendedSessionProgress,
            this.recommendedSessionProgress))
        .thenAccept(material -> {
            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
            this.anchorNode.setRenderable(nodeRenderable);
            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

            uploadCloudAnchorAsync(cloudAnchor)
                .thenAccept(id -> {
                    this.anchorId = id;
                    Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                    runOnUiThread(() -> {
                        MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                            .thenAccept(blueMaterial -> {
                                this.nodeRenderable.setMaterial(blueMaterial);
                                synchronized (this.syncTaps) {
                                    this.tapExecuted = false;
                                }
                            });
                    });
                });
        });
}

Agora, vamos conectar o código que será invocado quando a âncora que estamos consultando for localizada.Now, let's hook the code that will be invoked when the anchor we're querying for is located. Em seu método initializeSession(), adicione o código a seguir.Inside your initializeSession() method, add the following code. Este snippet de código criará uma esfera verde e a posicionará quando a âncora espacial de nuvem for localizada.This snippet will create & place a green sphere once the cloud spatial anchor is located. Ele também habilitará o toque na tela novamente, portanto, você poderá repetir o cenário completo mais uma vez: criar outra âncora local, fará o upload dela e a localizará novamente.It will also enable screen tapping again, so you can repeat the whole scenario once more: create another local anchor, upload it, and locate it again.

private void initializeSession() {
    if (this.cloudSession != null){
        this.cloudSession.close();
    }
    this.cloudSession = new CloudSpatialAnchorSession();
    this.cloudSession.setSession(sceneView.getSession());
    this.cloudSession.setLogLevel(SessionLogLevel.Information);
    this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
    this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

    this.cloudSession.addSessionUpdatedListener(args -> {
        synchronized (this.syncSessionProgress) {
            this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
            Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
            if (!this.scanningForUpload)
            {
                return;
            }
        }

        runOnUiThread(() -> {
            synchronized (this.syncSessionProgress) {
                MaterialFactory.makeOpaqueWithColor(this, new Color(
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress,
                        this.recommendedSessionProgress))
                    .thenAccept(material -> {
                        this.nodeRenderable.setMaterial(material);
                    });
            }
        });
    });

    this.cloudSession.addAnchorLocatedListener(args -> {
        if (args.getStatus() == LocateAnchorStatus.Located)
        {
            runOnUiThread(()->{
                this.anchorNode = new AnchorNode();
                this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                    .thenAccept(greenMaterial -> {
                        this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                        this.anchorNode.setRenderable(nodeRenderable);
                        this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                        this.anchorId = null;
                        synchronized (this.syncTaps) {
                            this.tapExecuted = false;
                        }
                    });
            });
        }
    });

    this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
    this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
    this.cloudSession.start();
}

É isso!That's it! Reimplante seu aplicativo uma última vez para experimentar o cenário completo de ponta a ponta.Redeploy your app one last time to try out the whole scenario end to end. Mova seu dispositivo e posicione sua esfera preta.Move around your device, and place your black sphere. Em seguida, continuaremos movendo seu dispositivo para capturar quadros da câmera até que a esfera fique amarela.Then, keep moving your device to capture camera frames until the sphere turns yellow. A âncora local será carregada e a esfera ficará azul.Your local anchor will be uploaded, and your sphere will turn blue. Por fim, toque em sua tela mais uma vez para que sua âncora local seja removida e, em seguida, consultaremos em busca de seu equivalente de nuvem.Finally, tap your screen once more, so that your local anchor is removed, and then we'll query for its cloud counterpart. Continue movendo seu dispositivo até que a âncora espacial de nuvem seja localizada.Continue moving your device around until your cloud spatial anchor is located. Uma esfera verde deve aparecer na localização correta e você pode limpar e repetir o cenário completo.A green sphere should appear in the correct location, and you can rinse & repeat the whole scenario again.

Juntando tudoPutting everything together

Vemos aqui como deve ser a aparência do arquivo de classe MainActivity completo após todos os elementos diferentes terem sido colocados juntos.Here is how the complete MainActivity class file should look like, after all the different elements have been put together. Você pode usar isso como uma referência para comparar com seu próprio arquivo e identificar se você tem alguma diferença restante.You can use it as a reference to compare against your own file, and spot if you may have any differences left.

package com.example.myfirstapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.math.Vector3;
import com.google.ar.sceneform.rendering.Color;
import com.google.ar.sceneform.rendering.MaterialFactory;
import com.google.ar.sceneform.rendering.Renderable;
import com.google.ar.sceneform.rendering.ShapeFactory;
import com.google.ar.sceneform.ux.ArFragment;

import android.view.MotionEvent;
import android.util.Log;

import com.google.ar.sceneform.ArSceneView;
import com.google.ar.sceneform.Scene;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchor;
import com.microsoft.azure.spatialanchors.CloudSpatialAnchorSession;
import com.microsoft.azure.spatialanchors.SessionLogLevel;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.microsoft.azure.spatialanchors.AnchorLocateCriteria;
import com.microsoft.azure.spatialanchors.LocateAnchorStatus;

public class MainActivity extends AppCompatActivity {

    private boolean tapExecuted = false;
    private final Object syncTaps = new Object();
    private ArFragment arFragment;
    private AnchorNode anchorNode;
    private Renderable nodeRenderable = null;
    private float recommendedSessionProgress = 0f;

    private ArSceneView sceneView;
    private CloudSpatialAnchorSession cloudSession;

    private String anchorId = null;
    private boolean scanningForUpload = false;
    private final Object syncSessionProgress = new Object();
    private ExecutorService executorService = Executors.newSingleThreadExecutor();

    // <onCreate>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
        this.arFragment.setOnTapArPlaneListener(this::handleTap);

        this.sceneView = arFragment.getArSceneView();
        Scene scene = sceneView.getScene();
        scene.addOnUpdateListener(frameTime -> {
            if (this.cloudSession != null) {
                this.cloudSession.processFrame(sceneView.getArFrame());
            }
        });

        initializeSession();
    }
    // </onCreate>

    // <initializeSession>
    private void initializeSession() {
        if (this.cloudSession != null){
            this.cloudSession.close();
        }
        this.cloudSession = new CloudSpatialAnchorSession();
        this.cloudSession.setSession(sceneView.getSession());
        this.cloudSession.setLogLevel(SessionLogLevel.Information);
        this.cloudSession.addOnLogDebugListener(args -> Log.d("ASAInfo", args.getMessage()));
        this.cloudSession.addErrorListener(args -> Log.e("ASAError", String.format("%s: %s", args.getErrorCode().name(), args.getErrorMessage())));

        this.cloudSession.addSessionUpdatedListener(args -> {
            synchronized (this.syncSessionProgress) {
                this.recommendedSessionProgress = args.getStatus().getRecommendedForCreateProgress();
                Log.i("ASAInfo", String.format("Session progress: %f", this.recommendedSessionProgress));
                if (!this.scanningForUpload)
                {
                    return;
                }
            }

            runOnUiThread(() -> {
                synchronized (this.syncSessionProgress) {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress,
                            this.recommendedSessionProgress))
                        .thenAccept(material -> {
                            this.nodeRenderable.setMaterial(material);
                        });
                }
            });
        });

        this.cloudSession.addAnchorLocatedListener(args -> {
            if (args.getStatus() == LocateAnchorStatus.Located)
            {
                runOnUiThread(()->{
                    this.anchorNode = new AnchorNode();
                    this.anchorNode.setAnchor(args.getAnchor().getLocalAnchor());
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.GREEN))
                        .thenAccept(greenMaterial -> {
                            this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), greenMaterial);
                            this.anchorNode.setRenderable(nodeRenderable);
                            this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                            this.anchorId = null;
                            synchronized (this.syncTaps) {
                                this.tapExecuted = false;
                            }
                        });
                });
            }
        });

        this.cloudSession.getConfiguration().setAccountId(/* Copy your account Identifier in here */);
        this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
        this.cloudSession.start();
    }
    // </initializeSession>

    // <handleTap>
    protected void handleTap(HitResult hitResult, Plane plane, MotionEvent motionEvent) {
        synchronized (this.syncTaps) {
            if (this.tapExecuted) {
                return;
            }

            this.tapExecuted = true;
        }

        if (this.anchorId != null) {
            this.anchorNode.getAnchor().detach();
            this.anchorNode.setParent(null);
            this.anchorNode = null;
            initializeSession();
            AnchorLocateCriteria criteria = new AnchorLocateCriteria();
            criteria.setIdentifiers(new String[]{this.anchorId});
            cloudSession.createWatcher(criteria);
            return;
        }

        this.anchorNode = new AnchorNode();
        this.anchorNode.setAnchor(hitResult.createAnchor());
        CloudSpatialAnchor cloudAnchor = new CloudSpatialAnchor();
        cloudAnchor.setLocalAnchor(this.anchorNode.getAnchor());

        MaterialFactory.makeOpaqueWithColor(this, new Color(
                this.recommendedSessionProgress,
                this.recommendedSessionProgress,
                this.recommendedSessionProgress))
            .thenAccept(material -> {
                this.nodeRenderable = ShapeFactory.makeSphere(0.1f, new Vector3(0.0f, 0.15f, 0.0f), material);
                this.anchorNode.setRenderable(nodeRenderable);
                this.anchorNode.setParent(arFragment.getArSceneView().getScene());

                uploadCloudAnchorAsync(cloudAnchor)
                    .thenAccept(id -> {
                        this.anchorId = id;
                        Log.i("ASAInfo", String.format("Cloud Anchor created: %s", this.anchorId));
                        runOnUiThread(() -> {
                            MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.BLUE))
                                .thenAccept(blueMaterial -> {
                                    this.nodeRenderable.setMaterial(blueMaterial);
                                    synchronized (this.syncTaps) {
                                        this.tapExecuted = false;
                                    }
                                });
                        });
                    });
            });
    }
    // </handleTap>

    // <uploadCloudAnchorAsync>
    private CompletableFuture<String> uploadCloudAnchorAsync(CloudSpatialAnchor anchor) {
        synchronized (this.syncSessionProgress) {
            this.scanningForUpload = true;
        }

        return CompletableFuture.runAsync(() -> {
            try {
                float currentSessionProgress;
                do {
                    synchronized (this.syncSessionProgress) {
                        currentSessionProgress = this.recommendedSessionProgress;
                    }
                    if (currentSessionProgress < 1.0) {
                        Thread.sleep(500);
                    }
                }
                while (currentSessionProgress < 1.0);

                synchronized (this.syncSessionProgress) {
                    this.scanningForUpload = false;
                }
                runOnUiThread(() -> {
                    MaterialFactory.makeOpaqueWithColor(this, new Color(android.graphics.Color.YELLOW))
                        .thenAccept(yellowMaterial -> {
                            this.nodeRenderable.setMaterial(yellowMaterial);
                        });
                });

                this.cloudSession.createAnchorAsync(anchor).get();
            } catch (InterruptedException | ExecutionException e) {
                Log.e("ASAError", e.toString());
                throw new RuntimeException(e);
            }
        }, executorService).thenApply(ignore -> anchor.getIdentifier());
    }
    // </uploadCloudAnchorAsync>
}

Próximas etapasNext steps

Neste tutorial, você viu como criar um novo aplicativo Android que se integra à funcionalidade de ARCore com Âncoras Espaciais do Azure.In this tutorial, you've seen how to create a new Android app that integrates ARCore functionality with Azure Spatial Anchors. Para saber mais sobre a biblioteca de Âncoras Espaciais do Azure, veja nosso guia sobre como criar e localizar âncoras.To learn more about the Azure Spatial Anchors library, continue to our guide on how to create and locate anchors.