Esercitazione: Istruzioni dettagliate per la creazione di una nuova app Android usando Ancoraggi nello spazio di AzureTutorial: Step-by-step instructions to create a new Android app using Azure Spatial Anchors

Questa esercitazione illustra come creare una nuova app Android che integra funzionalità ARCore con Ancoraggi nello spazio di Azure.This tutorial will show you how to create a new Android app that integrates ARCore functionality with Azure Spatial Anchors.

PrerequisitiPrerequisites

Per completare questa esercitazione, accertarsi di avere:To complete this tutorial, make sure you have:

IntroduzioneGetting started

Avviare Android Studio.Start Android Studio. Nella finestra iniziale di Android Studio selezionare Start a new Android Studio project (Avvia un nuovo progetto di Android Studio).In the Welcome to Android Studio window, click Start a new Android Studio project. Oppure, se è già aperto un progetto, selezionare File->New Project (Nuovo progetto).Or, if you have a project already opened, select File->New Project.

Nella finestra Phone and Tablet (Telefono e tablet) della finestra Create New Project (Crea nuovo progetto) scegliere Empty Activity (Attività vuota) e fare clic su Avanti.In the Create New Project window, under the Phone and Tablet section, choose Empty Activity, and click Next. Quindi, in Minimum API level (Livello minimo API), scegliere API 26: Android 8.0 (Oreo) e assicurarsi che l'opzione Language (Linguaggio) sia impostata su Java.Then, under Minimum API level, choose API 26: Android 8.0 (Oreo), and ensure the Language is set to Java. Si può scegliere di cambiare il nome e la posizione del progetto, oltre al nome del pacchetto.You may want to change the Project Name & Location, and the Package name. Lasciare inalterate le altre opzioni.Leave the other options as they are. Fare clic su Fine.Click Finish. Verrà eseguito il programma di installazione del componente.The Component Installer will run. Al termine, fare clic su Fine.Once it's done, click Finish. Dopo un certo tempo di elaborazione, Android Studio aprirà l'IDE.After some processing, Android Studio will open the IDE.

Prova praticaTrying it out

Per testare la nuova app, connettere il dispositivo abilitato per lo sviluppo al computer di sviluppo con un cavo USB.To test out your new app, connect your developer-enabled device to your development machine with a USB cable. Fare clic su Run (Esegui) ->Run 'app' (Esegui 'app') .Click Run->Run 'app'. Nella finestra Select Deployment Target (Selezionare la destinazione della distribuzione) selezionare il dispositivo e fare clic su OK.In the Select Deployment Target window, select your device, and click OK. Android Studio installa l'app nel dispositivo connesso e la avvia.Android Studio installs the app on your connected device and starts it. Dovrebbe essere visualizzato "Hello World!"You should now see "Hello World!" nell'app in esecuzione nel dispositivo.displayed in the app running on your device. Fare clic su Run (Esegui) ->Stop 'app' (Arresta 'app') .Click Run->Stop 'app'.

Integrazione di ARCoreIntegrating ARCore

ARCore è la piattaforma di Google per lo sviluppo di esperienze di realtà aumentata, che consente al dispositivo di tenere traccia della propria posizione mentre si muove e inizia a riconoscere il mondo reale.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.

Modificare app\manifests\AndroidManifest.xml per includere le voci seguenti nel nodo <manifest> radice.Modify app\manifests\AndroidManifest.xml to include the following entries inside the root <manifest> node. Questo frammento di codice esegue alcune operazioni:This code snippet does a few things:

  • Consente all'app di accedere alla fotocamera del dispositivo.It will allow your app to access your device camera.
  • Assicura inoltre che l'app sia visibile in Google Play Store solo per i dispositivi che supportano ARCore.It will also ensure your app is only visible in the Google Play Store to devices that support ARCore.
  • Configura Google Play Store per scaricare e installare ARCore, se non è già installato, quando viene installata l'app.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>

Modificare Gradle Scripts\build.gradle (Module: app) per includere la voce seguente.Modify Gradle Scripts\build.gradle (Module: app) to include the following entry. Questo codice assicura che l'app sia destinata ad ARCore versione 1.8.This code will ensure that your app targets ARCore version 1.8. Dopo aver apportato questa modifica, è possibile che venga visualizzata una notifica di Gradle che chiede di eseguire la sincronizzazione. Fare clic su Sync now (Sincronizza ora).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'
    ...
}

Integrazione di SceneformIntegrating Sceneform

Sceneform semplifica il rendering di scene 3D realistiche nelle app di realtà aumentata, senza la necessità di competenze in OpenGL.Sceneform makes it simple to render realistic 3D scenes in Augmented Reality apps, without having to learn OpenGL.

Modificare Gradle Scripts\build.gradle (Module: app) per includere le voci seguenti.Modify Gradle Scripts\build.gradle (Module: app) to include the following entries. Questo codice consente all'app di usare i costrutti del linguaggio Java 8, richiesti da Sceneform.This code will allow your app to use language constructs from Java 8, which Sceneform requires. Assicura inoltre che l'app sia destinata alla versione 1.8 di Sceneform, perché deve corrispondere alla versione di ARCore usata dall'app.It will also ensure your app targets Sceneform version 1.8, since it should match the version of ARCore your app is using. Dopo aver apportato questa modifica, è possibile che venga visualizzata una notifica di Gradle che chiede di eseguire la sincronizzazione. Fare clic su Sync now (Sincronizza ora).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'
    ...
}

Aprire app\res\layout\activity_main.xml e sostituire l'elemento <TextView> Hello Word con l'elemento ArFragment seguente.Open your app\res\layout\activity_main.xml, and replace the existing Hello Wolrd <TextView> element with the following ArFragment. Questo codice causa la visualizzazione del feed della fotocamera sullo schermo, consentendo ad ARCore di tenere traccia della posizione del dispositivo mentre si muove.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" />

Ridistribuire l'app nel dispositivo per verificarla ancora una volta.Redeploy your app to your device to validate it once more. Questa volta, dovrebbero essere chieste le autorizzazioni per la fotocamera.This time, you should be asked for camera permissions. Dopo l'approvazione, sullo schermo dovrebbe essere visualizzato il feed della fotocamera.Once approved, you should see your camera feed rendering on your screen.

Posizionare un oggetto nel mondo realePlace an object in the real world

Creare e posizionare un oggetto con l'app.Let's create & place an object using your app. Prima di tutto, aggiungere le istruzioni import seguenti in 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;

Quindi aggiungere le variabili membro seguenti nella 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;

Aggiungere poi il codice seguente nel metodo onCreate() di app\java\<PackageName>\MainActivity.Next, add the following code into your app\java\<PackageName>\MainActivity onCreate() method. Questo codice assocerà un listener, denominato handleTap(), che rileverà il tocco dell'utente sullo schermo del dispositivo.This code will hook up a listener, called handleTap(), that will detect when the user taps the screen on your device. Se il tocco avviene su una superficie del mondo reale che è già stata riconosciuta dal rilevamento di ARCore, il listener verrà eseguito.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);
    });

Infine, aggiungere il metodo handleTap() seguente, che collegherà tutti gli elementi tra loro.Finally, add the following handleTap() method, that will tie everything together. Viene creata una sfera, che viene posizionata nel punto toccato.It will create a sphere, and place it on the tapped location. Inizialmente la sfera sarà nera, perché this.recommendedSessionProgress è impostato su zero per il momento.The sphere will initially be black, since this.recommendedSessionProgress is set to zero right now. Questo valore verrà modificato in seguito.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 -> {
                            });
                    });

Ridistribuire l'app nel dispositivo per verificarla ancora una volta.Redeploy your app to your device to validate it once more. Questa volta, è possibile muovere il dispositivo per fare in modo che ARCore inizi a riconoscere l'ambiente.This time, you can move around your device to get ARCore to start recognizing your environment. Quindi, toccare lo schermo per creare e posizionare la sfera nera sopra la superficie scelta.Then, tap the screen to create & place your black sphere over the surface of your choice.

Collegare un ancoraggio nello spazio di Azure localeAttach a local Azure Spatial Anchor

Modificare Gradle Scripts\build.gradle (Module: app) per includere la voce seguente.Modify Gradle Scripts\build.gradle (Module: app) to include the following entry. Questo codice assicura che l'app sia destinata ad Ancoraggi nello spazio di Azure versione 2.2.0.This code will ensure that your app targets Azure Spatial Anchors version 2.2.0. In ogni caso, il riferimento a qualsiasi versione recente di Ancoraggi nello spazio di Azure dovrebbe funzionare.That said, referencing any recent version of Azure Spatial Anchors should work. Le note sulla versione sono disponibili qui.You can find the release notes here.

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

Fare clic con il pulsante destro del mouse su app\java\<PackageName>->New (Nuovo) ->Java Class (Classe Java) .Right-click app\java\<PackageName>->New->Java Class. Impostare Name (Nome) su MyFirstApp e Superclass (Superclasse) su android.app.Application.Set Name to MyFirstApp, and Superclass to android.app.Application. Lasciare inalterate le altre opzioni.Leave the other options as they are. Fare clic su OK.Click OK. Verrà creato un file denominato MyFirstApp.java.A file called MyFirstApp.java will be created. Aggiungere l'istruzione import seguente:Add the following import to it:

import com.microsoft.CloudServices;

Aggiungere quindi il codice seguente all'interno della nuova classe MyFirstApp, che assicura l'inizializzazione di Ancoraggi nello spazio di Azure con il contesto dell'applicazione.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);
    }

Modificare ora app\manifests\AndroidManifest.xml per includere la voce seguente nel nodo <application> radice.Now, modify app\manifests\AndroidManifest.xml to include the following entry inside the root <application> node. Questo codice associa la classe Application creata nell'app.This code will hook up the Application class you created into your app.

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

Tornare in app\java\<PackageName>\MainActivity e aggiungere le istruzioni import seguenti: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;

Quindi aggiungere le variabili membro seguenti nella 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;

Aggiungere poi il metodo initializeSession() seguente nella classe mainActivity.Next, let's add the following initializeSession() method inside your mainActivity class. Quando viene chiamato, questo metodo assicura che durante l'avvio dell'app venga creata e inizializzata correttamente una sessione di Ancoraggi nello spazio di Azure.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().setAccountKey(/* Copy your account Key in here */);

Associare quindi il metodo initializeSession() al metodo onCreate().Now, let's hook your initializeSession() method into your onCreate() method. Assicurarsi inoltre che i fotogrammi provenienti dal feed della fotocamera vengano inviati all'SDK di Ancoraggi nello spazio di Azure per l'elaborazione.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());
        }
    });

Infine, aggiungere il codice seguente nel metodo handleTap().Finally, add the following code into your handleTap() method. Il codice collega un ancoraggio nello spazio di Azure locale alla sfera nera posizionata nel mondo reale.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 -> {
                            });
                    });

Ridistribuire ancora una volta l'app.Redeploy your app once more. Muovere il dispositivo, toccare lo schermo e posizionare una sfera nera.Move around your device, tap the screen, and place a black sphere. Questa volta, però, il codice crea e collega un ancoraggio nello spazio di Azure locale alla sfera.This time, though, your code will be creating and attaching a local Azure Spatial Anchor to your sphere.

Prima di procedere, sarà necessario creare un account di Ancoraggi nello spazio di Azure per ottenere l'identificatore, la chiave e il dominio dell'account, se non si hanno già.Before proceeding any further, you'll need to create an Azure Spatial Anchors account to get the account Identifier, Key, and Domain, if you don't already have them. Per ottenerli, seguire la sezione seguente.Follow the following section to obtain them.

Creare una risorsa di Ancoraggi nello spazioCreate a Spatial Anchors resource

Accedere al portale di Azure.Go to the Azure portal.

Nel riquadro sinistro selezionare Crea una risorsa.On the left pane, select Create a resource.

Digitare Ancoraggi nello spazio nella casella di ricerca.Use the search box to search for Spatial Anchors.

Screenshot che mostra i risultati di una ricerca di ancoraggi nello spazio.

Selezionare Ancoraggi nello spazio, quindi selezionare Crea.Select Spatial Anchors, and then select Create.

Nel riquadro Account ancoraggi nello spazio procedere come segue:On the Spatial Anchors Account pane, do the following:

  • Immettere un nome di risorsa univoco usando i normali caratteri alfanumerici.Enter a unique resource name by using regular alphanumeric characters.

  • Selezionare la sottoscrizione a cui collegare la risorsa.Select the subscription that you want to attach the resource to.

  • Creare un gruppo di risorse selezionando Crea nuovo.Create a resource group by selecting Create new. Assegnare al gruppo il nome myResourceGroup e quindi selezionare OK.Name it myResourceGroup, and then select OK.

    Un gruppo di risorse è un contenitore logico in cui vengono distribuite e gestite risorse di Azure come app Web, database e account di archiviazione.A resource group is a logical container into which Azure resources, such as web apps, databases, and storage accounts, are deployed and managed. Ad esempio, si può scegliere in un secondo momento di eliminare l'intero gruppo di risorse in un unico semplice passaggio.For example, you can choose to delete the entire resource group in one simple step later.

  • Selezionare un'area in cui inserire la risorsa.Select a location (region) in which to place the resource.

  • Selezionare Nuovo per iniziare a creare la risorsa.Select New to begin creating the resource.

Screenshot del riquadro Ancoraggi nello spazio per la creazione di una risorsa.

Dopo aver creato la risorsa, il portale di Azure indica che la distribuzione è stata completata.After the resource is created, the Azure portal shows that your deployment is complete.

Screenshot che mostra la distribuzione della risorsa completata.

Selezionare Vai alla risorsa.Select Go to resource. È ora possibile visualizzare le proprietà della risorsa.You can now view the resource properties.

Copiare il valore di ID account della risorsa in un editor di testo per un uso successivo.Copy the resource's Account ID value into a text editor for later use.

Screenshot del riquadro di proprietà della risorsa.

Copiare anche il valore di Dominio account della risorsa in un editor di testo per un uso successivo.Also copy the resource's Account Domain value into a text editor for later use.

Screenshot che mostra il valore di Dominio account della risorsa.

In Impostazioni selezionare Chiave.Under Settings, select Key. Copiare i valori di Chiave primaria e Chiave dell'account in un editor di testo per un uso successivo.Copy the Primary key value, Account Key, into a text editor for later use.

Screenshot del riquadro delle chiavi per l'account.

Caricare l'ancoraggio locale nel cloudUpload your local anchor into the cloud

Dopo aver ottenuto l'identificatore, la chiave e il dominio dell'account di Ancoraggi nello spazio di Azure, è possibile tornare in app\java\<PackageName>\MainActivity e aggiungervi le istruzioni import seguenti:Once you have your Azure Spatial Anchors account Identifier, Key, and Domain, 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;

Quindi aggiungere le variabili membro seguenti nella 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;

Aggiungere poi il codice seguente nel metodo initializeSession().Now, add the following code into your initializeSession() method. Questo codice consente prima di tutto all'app di monitorare lo stato di avanzamento dell'SDK Ancoraggi nello spazio di Azure mentre raccoglie i fotogrammi dal feed della fotocamera.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. Durante questo processo, il colore della sfera inizia a cambiare dal nero originale al grigio.As it does, the color of your sphere will start changing from its original black, into grey. Quindi diventerà bianca quando sarà stato raccolto un numero di fotogrammi sufficiente per inviare l'ancoraggio nel cloud.Then, it will turn white once enough frames are collected to submit your anchor to the cloud. Il codice fornirà poi le credenziali necessarie per comunicare con il back-end del cloud.Second, this code will provide the credentials needed to communicate with the cloud back-end. Ecco dove configurare l'app per usare l'identificatore, la chiave e il dominio dell'account.Here is where you'll configure your app to use your account Identifier, Key, and Domain. Questi dati sono stati copiati in un editor di testo durante la configurazione della risorsa Ancoraggi nello spazio.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 */);
    this.cloudSession.getConfiguration().setAccountKey(/* Copy your account Key in here */);
    this.cloudSession.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);

Aggiungere poi il metodo uploadCloudAnchorAsync() seguente nella classe mainActivity.Next, add the following uploadCloudAnchorAsync() method inside your mainActivity class. Quando viene chiamato, questo metodo aspetta in modo asincrono finché non viene raccolto un numero di fotogrammi sufficiente dal dispositivo.Once called, this method will asynchronously wait until enough frames are collected from your device. Non appena ciò avviene, trasforma il colore della sfera in giallo e quindi avvia il caricamento dell'ancoraggio nello spazio di Azure locale nel cloud.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. Al termine del caricamento, il codice restituisce un identificatore di ancoraggio.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());
}

Infine, associare tutti gli elementi tra loro.Finally, let's hook everything together. Aggiungere il codice seguente nel metodo handleTap().In your handleTap() method, add the following code. Il codice richiama il metodo uploadCloudAnchorAsync() non appena verrà creata la sfera.It will invoke your uploadCloudAnchorAsync() method as soon as your sphere is created. Quando il metodo restituisce il risultato, il codice seguente esegue l'aggiornamento finale della sfera, cambiandone il colore in blu.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;
                                }
                            });
                    });

Ridistribuire ancora una volta l'app.Redeploy your app once more. Muovere il dispositivo, toccare lo schermo e posizionare la sfera.Move around your device, tap the screen, and place your sphere. Questa volta, però, il colore della sfera cambia dal nero verso il bianco, mentre vengono raccolti i fotogrammi dalla fotocamera.This time, though, your sphere will change its color from black towards white, as camera frames are collected. Quando il numero di fotogrammi è sufficiente, la sfera diventerà gialla e verrà avviato il caricamento nel cloud.Once we have enough frames, the sphere will turn into yellow, and the cloud upload will start. Al termine del caricamento, la sfera diventerà blu.Once the upload finishes, your sphere will turn blue. Facoltativamente, è anche possibile usare la finestra Logcat all'interno di Android Studio per monitorare i messaggi di log inviati dall'app,Optionally, you could also use the Logcat window inside Android Studio to monitor the log messages your app is sending. ad esempio lo stato di avanzamento della sessione durante l'acquisizione dei fotogrammi e l'identificatore dell'ancoraggio restituito dal cloud al termine del caricamento.For example, the session progress during frame captures, and the anchor identifier that the cloud returns once the upload is completed.

Individuare l'ancoraggio nello spazio nel cloudLocate your cloud spatial anchor

Dopo aver caricato l'ancoraggio nel cloud, è possibile provare a individuarlo di nuovo.One your anchor is uploaded to the cloud, we're ready to attempt locating it again. Prima di tutto, aggiungere le istruzioni import seguenti nel codice.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;

Quindi, aggiungere il codice seguente nel metodo handleTap().Then, let's add the following code into your handleTap() method. Il codice consentirà di:This code will:

  • Rimuovere l'attuale sfera blu dallo schermo.Remove our existing blue sphere from the screen.
  • Inizializzare di nuovo la sessione di Ancoraggi nello spazio di Azure.Initialize our Azure Spatial Anchors session again. Questa operazione assicura che l'ancoraggio da individuare proviene dal cloud e non è l'ancoraggio locale creato.This action will ensure that the anchor we're going to locate comes from the cloud instead of the local anchor we created.
  • Eseguire una query per trovare l'ancoraggio caricato nel cloud.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;
                                }
                            });
                    });
                });
        });
}

Associare ora il codice che verrà richiamato quando verrà individuato l'ancoraggio tramite la query.Now, let's hook the code that will be invoked when the anchor we're querying for is located. Aggiungere il codice seguente nel metodo initializeSession().Inside your initializeSession() method, add the following code. Questo frammento di codice crea e posiziona una sfera verde quando verrà individuato l'ancoraggio nello spazio nel cloud.This snippet will create & place a green sphere once the cloud spatial anchor is located. Consente inoltre di toccare di nuovo lo schermo, quindi sarà possibile ripetere ancora una volta l'intero scenario, ossia creare un altro ancoraggio locale, caricarlo e individuarlo.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.getConfiguration().setAccountDomain(/* Copy your account Domain in here */);
    this.cloudSession.start();
}

L'operazione è terminata.That's it! Ridistribuire l'app un'ultima volta per provare l'intero scenario completato.Redeploy your app one last time to try out the whole scenario end to end. Muovere il dispositivo e posizionare la sfera nera.Move around your device, and place your black sphere. Quindi continuare a muovere il dispositivo per acquisire i fotogrammi della fotocamera finché la sfera non diventa gialla.Then, keep moving your device to capture camera frames until the sphere turns yellow. L'ancoraggio locale verrà caricato e la sfera diventerà blu.Your local anchor will be uploaded, and your sphere will turn blue. Infine, toccare ancora una volta lo schermo in modo da rimuovere l'ancoraggio locale ed eseguire una query per trovare la controparte nel cloud.Finally, tap your screen once more, so that your local anchor is removed, and then we'll query for its cloud counterpart. Continuare a muovere il dispositivo finché non viene individuato l'ancoraggio nello spazio nel cloud.Continue moving your device around until your cloud spatial anchor is located. Dovrebbe comparire una sfera verde nella posizione corretta ed è possibile pulire e ripetere di nuovo l'intero scenario.A green sphere should appear in the correct location, and you can rinse & repeat the whole scenario again.

Riunire tutti gli elementiPutting everything together

Ecco come dovrebbe essere il file di classe MainActivity completo dopo aver riunito tutti i diversi elementi.Here is how the complete MainActivity class file should look like, after all the different elements have been put together. È possibile usarlo come riferimento da confrontare con il proprio file e verificare se sono state lasciate differenze.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.getConfiguration().setAccountDomain(/* Copy your account Domain 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>
}

Passaggi successiviNext steps

Questa esercitazione ha illustrato come creare una nuova app Android che integra funzionalità ARCore con Ancoraggi nello spazio di Azure.In this tutorial, you've seen how to create a new Android app that integrates ARCore functionality with Azure Spatial Anchors. Per altre informazioni sulla libreria di Ancoraggi nello spazio di Azure, passare alla guida che illustra come creare e individuare ancoraggi.To learn more about the Azure Spatial Anchors library, continue to our guide on how to create and locate anchors.