Esercitazione: Accedere agli utenti e chiamare l'API Microsoft Graph da un'applicazione Android

In questa esercitazione si creerà un'app Android che si integra con l'ID Microsoft Entra per accedere agli utenti e ottenere un token di accesso per chiamare l'API Microsoft Graph.

Dopo aver completato questa esercitazione, l'applicazione accetta gli accessi di account Microsoft personali (inclusi outlook.com, live.com e altri) e account aziendali o dell'istituto di istruzione di qualsiasi azienda o organizzazione che usa Microsoft Entra ID.

Contenuto dell'esercitazione:

  • Creare un progetto di app Android in Android Studio
  • Registrare l'app nell'interfaccia di amministrazione di Microsoft Entra
  • Aggiungere il codice per supportare l'accesso e la disconnessione
  • Aggiungere il codice per chiamare l'API Microsoft Graph
  • Testare l'app

Prerequisiti

Contenuto dell'esercitazione

Screenshot del funzionamento dell'app di esempio generata da questa esercitazione.

L'app in questa esercitazione accede agli utenti e ottiene i dati per loro conto. Questi dati sono accessibili tramite un'API protetta (API Microsoft Graph) che richiede l'autorizzazione ed è protetta da Microsoft Identity Platform.

Questo esempio usa Microsoft Authentication Library (MSAL) per Android per implementare l'autenticazione: com.microsoft.identity.client.

Creare un progetto

Se non si ha già un'applicazione Android, seguire questa procedura per creare un nuovo progetto.

  1. Aprire Android Studio e selezionare Start a new Android Studio project (Avvia un nuovo progetto di Android Studio).
  2. Selezionare Basic Activity (Attività di base) e quindi Next (Avanti).
  3. Immettere un nome per l'applicazione, ad esempio MSALAndroidapp.
  4. Registrare il nome del pacchetto da usare nei passaggi successivi.
  5. Modificare il linguaggio da Kotlin a Java.
  6. Impostare il livello API Minimo SDK su API 16 o versione successiva e selezionare Fine.

Registrare un'applicazione con Microsoft Entra ID

Suggerimento

I passaggi descritti in questo articolo possono variare leggermente in base al portale da cui si inizia.

  1. Accedere all'Interfaccia di amministrazione di Microsoft Entra almeno come sviluppatore di applicazioni.

  2. Se si ha accesso a più tenant, usare l’icona Impostazioni nel menu in alto per passare al tenant in cui si desidera registrare l’applicazione nel menu Directory e sottoscrizioni.

  3. Passare a Identità>Applicazioni>Registrazioni app.

  4. Seleziona Nuova registrazione.

  5. Immettere un nome per l'applicazione. Tale nome, che potrebbe essere visualizzato dagli utenti dell'app, può essere modificato in un secondo momento.

  6. Per Tipi di account supportati selezionare Account in qualsiasi directory organizzativa (Qualsiasi directory Microsoft Entra - Multi-tenant) e account Microsoft personali (ad esempio Skype, Xbox). Per informazioni sui diversi tipi di account, selezionare l'opzione ?

  7. Selezionare Registra.

  8. In Gestisci selezionare Autenticazione>Aggiungi una piattaforma>Android.

  9. Immettere il nome del pacchetto del progetto. Se è stato scaricato il codice di esempio, il valore sarà com.azuresamples.msalandroidapp.

  10. Nella sezione Hash della firma del riquadro Configura l'app Android selezionare Generazione di un hash della firma di sviluppo e copiare il comando KeyTool nella riga di comando.

  11. Immettere il valore di Hash della firma generato da KeyTool.

  12. Selezionare Configura e salvare la configurazione MSAL visualizzata nel riquadro di configurazione android in modo da poterla immettere quando si configura l'app in un secondo momento.

  13. Selezionare Fatto.

Configurare l'applicazione

  1. Nel riquadro dei progetti di Android Studio passare a app\src\main\res.

  2. Fare clic con il pulsante destro del mouse su res e scegliere Nuovo>Directory. Immettere raw come nome della nuova directory e selezionare OK.

  3. In app>src>main>res>raw creare un nuovo file JSON denominato auth_config_single_account.json e incollare la configurazione MSAL salvata in precedenza.

    Sotto l'URI di reindirizzamento incollare:

      "account_mode" : "SINGLE",
    

    Il file di configurazione dovrebbe essere simile a questo esempio:

    {
      "client_id": "00001111-aaaa-bbbb-3333-cccc4444",
      "authorization_user_agent": "WEBVIEW",
      "redirect_uri": "msauth://com.azuresamples.msalandroidapp/00001111%cccc4444%3D",
      "broker_redirect_uri_registered": true,
      "account_mode": "SINGLE",
      "authorities": [
        {
          "type": "AAD",
          "audience": {
            "type": "AzureADandPersonalMicrosoftAccount",
            "tenant_id": "common"
          }
        }
      ]
    }
    

    Poiché questa esercitazione illustra solo come configurare un'app in modalità Account singolo, vedere Modalità account singolo e più account e come configurare l'app per altre informazioni

  4. È consigliabile usare "WEBVIEW". Se vuoi configurare "authorization_user_agent" come "BROWSER" nella tua app, devi apportare gli aggiornamenti seguenti. a) Aggiornare auth_config_single_account.json con "authorization_user_agent": "Browser". b) Aggiornare AndroidManifest.xml. Nell'app passare all'app >src>main>AndroidManifest.xml, aggiungere l'attività BrowserTabActivity come figlio dell'elemento.<application> Questa voce consente a Microsoft Entra ID di richiamare l'applicazione al termine dell'autenticazione:

    <!--Intent filter to capture System Browser or Authenticator calling back to our app after sign-in-->
    <activity
        android:name="com.microsoft.identity.client.BrowserTabActivity"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="msauth"
                android:host="Enter_the_Package_Name"
                android:path="/Enter_the_Signature_Hash" />
        </intent-filter>
    </activity>
    
    • Usare il nome del pacchetto per sostituire android:host=. il valore. Dovrebbe essere simile com.azuresamples.msalandroidappa .
    • Usare l'hash della firma per sostituire android:path= il valore. Assicurarsi che all'inizio dell'hash della firma sia presente un carattere iniziale / . Dovrebbe essere simile /aB1cD2eF3gH4+iJ5kL6-mN7oP8q=a .

    Questi valori sono disponibili anche nel pannello Autenticazione della registrazione dell'app.

Aggiungere MSAL e librerie pertinenti al progetto

  1. Nella finestra del progetto Android Studio passare a app>build.gradle e aggiungere le librerie seguenti nella sezione dipendenze:

     implementation 'com.microsoft.identity.client:msal:5.0.0'
     implementation 'com.android.volley:volley:1.2.1'
    
  2. Nella finestra del progetto Android Studio aprire settings.gradle e dichiarare il repository maven seguente nella sezione dependencyResolutionManagement>repository:

     maven {
          url 'https://pkgs.dev.azure.com/MicrosoftDeviceSDK/DuoSDK-Public/_packaging/Duo-SDK-Feed/maven/v1'
     }
    
  3. Selezionare Sincronizza ora nella barra di notifica.

Creare e aggiornare il frammento necessario

  1. In app>src>main>java>com.example(nome dell'app). Creare i frammenti Android seguenti:

    • MSGraphRequestWrapper
    • OnFragmentInteractionListener
    • SingleAccountModeFragment
  2. Aprire MSGraphRequestWrapper.java e sostituire il codice con il frammento di codice seguente per chiamare l'API Microsoft Graph usando il token fornito da MSAL:

     package com.azuresamples.msalandroidapp;
    
     import android.content.Context;
     import android.util.Log;
    
     import androidx.annotation.NonNull;
    
     import com.android.volley.DefaultRetryPolicy;
     import com.android.volley.Request;
     import com.android.volley.RequestQueue;
     import com.android.volley.Response;
     import com.android.volley.toolbox.JsonObjectRequest;
     import com.android.volley.toolbox.Volley;
    
     import org.json.JSONObject;
    
     import java.util.HashMap;
     import java.util.Map;
    
     public class MSGraphRequestWrapper {
         private static final String TAG = MSGraphRequestWrapper.class.getSimpleName();
    
         // See: https://docs.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints
         public static final String MS_GRAPH_ROOT_ENDPOINT = "https://graph.microsoft.com/";
    
         /**
          * Use Volley to make an HTTP request with
          * 1) a given MSGraph resource URL
          * 2) an access token
          * to obtain MSGraph data.
          **/
         public static void callGraphAPIUsingVolley(@NonNull final Context context,
                                                    @NonNull final String graphResourceUrl,
                                                    @NonNull final String accessToken,
                                                    @NonNull final Response.Listener<JSONObject> responseListener,
                                                    @NonNull final Response.ErrorListener errorListener) {
             Log.d(TAG, "Starting volley request to graph");
    
             /* Make sure we have a token to send to graph */
             if (accessToken == null || accessToken.length() == 0) {
                 return;
             }
    
             RequestQueue queue = Volley.newRequestQueue(context);
             JSONObject parameters = new JSONObject();
    
             try {
                 parameters.put("key", "value");
             } catch (Exception e) {
                 Log.d(TAG, "Failed to put parameters: " + e.toString());
             }
    
             JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, graphResourceUrl,
                     parameters, responseListener, errorListener) {
                 @Override
                 public Map<String, String> getHeaders() {
                     Map<String, String> headers = new HashMap<>();
                     headers.put("Authorization", "Bearer " + accessToken);
                     return headers;
                 }
             };
    
             Log.d(TAG, "Adding HTTP GET to Queue, Request: " + request.toString());
    
             request.setRetryPolicy(new DefaultRetryPolicy(
                     3000,
                     DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                     DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
             queue.add(request);
         }
     }
    
  3. Aprire OnFragmentInteractionListener.java e sostituire il codice con il frammento di codice seguente per consentire la comunicazione tra frammenti diversi:

     package com.azuresamples.msalandroidapp;
    
     /**
      * This interface must be implemented by activities that contain this
      * fragment to allow an interaction in this fragment to be communicated
      * to the activity and potentially other fragments contained in that
      * activity.
      * <p>
      * See the Android Training lesson <a href=
      * "http://developer.android.com/training/basics/fragments/communicating.html"
      * >Communicating with Other Fragments</a> for more information.
      */
     public interface OnFragmentInteractionListener {
     }
    
  4. Aprire SingleAccountModeFragment.java e sostituire il codice con il frammento di codice seguente per inizializzare un'applicazione con account singolo, caricare un account utente e ottenere un token per chiamare l'API Microsoft Graph:

     package com.azuresamples.msalandroidapp;
    
     import android.os.Bundle;
    
     import androidx.annotation.NonNull;
     import androidx.annotation.Nullable;
     import androidx.fragment.app.Fragment;
    
     import android.util.Log;
     import android.view.LayoutInflater;
     import android.view.View;
     import android.view.ViewGroup;
     import android.widget.Button;
     import android.widget.TextView;
     import android.widget.Toast;
    
     import com.android.volley.Response;
     import com.android.volley.VolleyError;
     import com.microsoft.identity.client.AuthenticationCallback;
     import com.microsoft.identity.client.IAccount;
     import com.microsoft.identity.client.IAuthenticationResult;
     import com.microsoft.identity.client.IPublicClientApplication;
     import com.microsoft.identity.client.ISingleAccountPublicClientApplication;
     import com.microsoft.identity.client.PublicClientApplication;
     import com.microsoft.identity.client.SilentAuthenticationCallback;
     import com.microsoft.identity.client.exception.MsalClientException;
     import com.microsoft.identity.client.exception.MsalException;
     import com.microsoft.identity.client.exception.MsalServiceException;
     import com.microsoft.identity.client.exception.MsalUiRequiredException;
    
     import org.json.JSONObject;
    
     /**
      * Implementation sample for 'Single account' mode.
      * <p>
      * If your app only supports one account being signed-in at a time, this is for you.
      * This requires "account_mode" to be set as "SINGLE" in the configuration file.
      * (Please see res/raw/auth_config_single_account.json for more info).
      * <p>
      * Please note that switching mode (between 'single' and 'multiple' might cause a loss of data.
      */
     public class SingleAccountModeFragment extends Fragment {
         private static final String TAG = SingleAccountModeFragment.class.getSimpleName();
    
         /* UI & Debugging Variables */
         Button signInButton;
         Button signOutButton;
         Button callGraphApiInteractiveButton;
         Button callGraphApiSilentButton;
         TextView scopeTextView;
         TextView graphResourceTextView;
         TextView logTextView;
         TextView currentUserTextView;
         TextView deviceModeTextView;
    
         /* Azure AD Variables */
         private ISingleAccountPublicClientApplication mSingleAccountApp;
         private IAccount mAccount;
    
         @Override
         public View onCreateView(LayoutInflater inflater,
                                  ViewGroup container,
                                  Bundle savedInstanceState) {
             // Inflate the layout for this fragment
             final View view = inflater.inflate(R.layout.fragment_single_account_mode, container, false);
             initializeUI(view);
    
             // Creates a PublicClientApplication object with res/raw/auth_config_single_account.json
             PublicClientApplication.createSingleAccountPublicClientApplication(getContext(),
                     R.raw.auth_config_single_account,
                     new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
                         @Override
                         public void onCreated(ISingleAccountPublicClientApplication application) {
                             /**
                              * This test app assumes that the app is only going to support one account.
                              * This requires "account_mode" : "SINGLE" in the config json file.
                              **/
                             mSingleAccountApp = application;
                             loadAccount();
                         }
    
                         @Override
                         public void onError(MsalException exception) {
                             displayError(exception);
                         }
                     });
    
             return view;
         }
    
         /**
          * Initializes UI variables and callbacks.
          */
         private void initializeUI(@NonNull final View view) {
             signInButton = view.findViewById(R.id.btn_signIn);
             signOutButton = view.findViewById(R.id.btn_removeAccount);
             callGraphApiInteractiveButton = view.findViewById(R.id.btn_callGraphInteractively);
             callGraphApiSilentButton = view.findViewById(R.id.btn_callGraphSilently);
             scopeTextView = view.findViewById(R.id.scope);
             graphResourceTextView = view.findViewById(R.id.msgraph_url);
             logTextView = view.findViewById(R.id.txt_log);
             currentUserTextView = view.findViewById(R.id.current_user);
             deviceModeTextView = view.findViewById(R.id.device_mode);
    
             final String defaultGraphResourceUrl = MSGraphRequestWrapper.MS_GRAPH_ROOT_ENDPOINT + "v1.0/me";
             graphResourceTextView.setText(defaultGraphResourceUrl);
    
             signInButton.setOnClickListener(new View.OnClickListener() {
                 public void onClick(View v) {
                     if (mSingleAccountApp == null) {
                         return;
                     }
    
                     mSingleAccountApp.signIn(getActivity(), null, getScopes(), getAuthInteractiveCallback());
                 }
             });
    
             signOutButton.setOnClickListener(new View.OnClickListener() {
                 public void onClick(View v) {
                     if (mSingleAccountApp == null) {
                         return;
                     }
    
                     /**
                      * Removes the signed-in account and cached tokens from this app (or device, if the device is in shared mode).
                      */
                     mSingleAccountApp.signOut(new ISingleAccountPublicClientApplication.SignOutCallback() {
                         @Override
                         public void onSignOut() {
                             mAccount = null;
                             updateUI();
                             showToastOnSignOut();
                         }
    
                         @Override
                         public void onError(@NonNull MsalException exception) {
                             displayError(exception);
                         }
                     });
                 }
             });
    
             callGraphApiInteractiveButton.setOnClickListener(new View.OnClickListener() {
                 public void onClick(View v) {
                     if (mSingleAccountApp == null) {
                         return;
                     }
    
                     /**
                      * If acquireTokenSilent() returns an error that requires an interaction (MsalUiRequiredException),
                      * invoke acquireToken() to have the user resolve the interrupt interactively.
                      *
                      * Some example scenarios are
                      *  - password change
                      *  - the resource you're acquiring a token for has a stricter set of requirement than your Single Sign-On refresh token.
                      *  - you're introducing a new scope which the user has never consented for.
                      */
                     mSingleAccountApp.acquireToken(getActivity(), getScopes(), getAuthInteractiveCallback());
                 }
             });
    
             callGraphApiSilentButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     if (mSingleAccountApp == null) {
                         return;
                     }
    
                     /**
                      * Once you've signed the user in,
                      * you can perform acquireTokenSilent to obtain resources without interrupting the user.
                      */
                     mSingleAccountApp.acquireTokenSilentAsync(getScopes(), mAccount.getAuthority(), getAuthSilentCallback());
                 }
             });
    
         }
    
         @Override
         public void onResume() {
             super.onResume();
    
             /**
              * The account may have been removed from the device (if broker is in use).
              *
              * In shared device mode, the account might be signed in/out by other apps while this app is not in focus.
              * Therefore, we want to update the account state by invoking loadAccount() here.
              */
             loadAccount();
         }
    
         /**
          * Extracts a scope array from a text field,
          * i.e. from "User.Read User.ReadWrite" to ["user.read", "user.readwrite"]
          */
         private String[] getScopes() {
             return scopeTextView.getText().toString().toLowerCase().split(" ");
         }
    
         /**
          * Load the currently signed-in account, if there's any.
          */
         private void loadAccount() {
             if (mSingleAccountApp == null) {
                 return;
             }
    
             mSingleAccountApp.getCurrentAccountAsync(new ISingleAccountPublicClientApplication.CurrentAccountCallback() {
                 @Override
                 public void onAccountLoaded(@Nullable IAccount activeAccount) {
                     // You can use the account data to update your UI or your app database.
                     mAccount = activeAccount;
                     updateUI();
                 }
    
                 @Override
                 public void onAccountChanged(@Nullable IAccount priorAccount, @Nullable IAccount currentAccount) {
                     if (currentAccount == null) {
                         // Perform a cleanup task as the signed-in account changed.
                         showToastOnSignOut();
                     }
                 }
    
                 @Override
                 public void onError(@NonNull MsalException exception) {
                     displayError(exception);
                 }
             });
         }
    
         /**
          * Callback used in for silent acquireToken calls.
          */
         private SilentAuthenticationCallback getAuthSilentCallback() {
             return new SilentAuthenticationCallback() {
    
                 @Override
                 public void onSuccess(IAuthenticationResult authenticationResult) {
                     Log.d(TAG, "Successfully authenticated");
    
                     /* Successfully got a token, use it to call a protected resource - MSGraph */
                     callGraphAPI(authenticationResult);
                 }
    
                 @Override
                 public void onError(MsalException exception) {
                     /* Failed to acquireToken */
                     Log.d(TAG, "Authentication failed: " + exception.toString());
                     displayError(exception);
    
                     if (exception instanceof MsalClientException) {
                         /* Exception inside MSAL, more info inside MsalError.java */
                     } else if (exception instanceof MsalServiceException) {
                         /* Exception when communicating with the STS, likely config issue */
                     } else if (exception instanceof MsalUiRequiredException) {
                         /* Tokens expired or no session, retry with interactive */
                     }
                 }
             };
         }
    
         /**
          * Callback used for interactive request.
          * If succeeds we use the access token to call the Microsoft Graph.
          * Does not check cache.
          */
         private AuthenticationCallback getAuthInteractiveCallback() {
             return new AuthenticationCallback() {
    
                 @Override
                 public void onSuccess(IAuthenticationResult authenticationResult) {
                     /* Successfully got a token, use it to call a protected resource - MSGraph */
                     Log.d(TAG, "Successfully authenticated");
                     Log.d(TAG, "ID Token: " + authenticationResult.getAccount().getClaims().get("id_token"));
    
                     /* Update account */
                     mAccount = authenticationResult.getAccount();
                     updateUI();
    
                     /* call graph */
                     callGraphAPI(authenticationResult);
                 }
    
                 @Override
                 public void onError(MsalException exception) {
                     /* Failed to acquireToken */
                     Log.d(TAG, "Authentication failed: " + exception.toString());
                     displayError(exception);
    
                     if (exception instanceof MsalClientException) {
                         /* Exception inside MSAL, more info inside MsalError.java */
                     } else if (exception instanceof MsalServiceException) {
                         /* Exception when communicating with the STS, likely config issue */
                     }
                 }
    
                 @Override
                 public void onCancel() {
                     /* User canceled the authentication */
                     Log.d(TAG, "User cancelled login.");
                 }
             };
         }
    
         /**
          * Make an HTTP request to obtain MSGraph data
          */
         private void callGraphAPI(final IAuthenticationResult authenticationResult) {
             MSGraphRequestWrapper.callGraphAPIUsingVolley(
                     getContext(),
                     graphResourceTextView.getText().toString(),
                     authenticationResult.getAccessToken(),
                     new Response.Listener<JSONObject>() {
                         @Override
                         public void onResponse(JSONObject response) {
                             /* Successfully called graph, process data and send to UI */
                             Log.d(TAG, "Response: " + response.toString());
                             displayGraphResult(response);
                         }
                     },
                     new Response.ErrorListener() {
                         @Override
                         public void onErrorResponse(VolleyError error) {
                             Log.d(TAG, "Error: " + error.toString());
                             displayError(error);
                         }
                     });
         }
    
         //
         // Helper methods manage UI updates
         // ================================
         // displayGraphResult() - Display the graph response
         // displayError() - Display the graph response
         // updateSignedInUI() - Updates UI when the user is signed in
         // updateSignedOutUI() - Updates UI when app sign out succeeds
         //
    
         /**
          * Display the graph response
          */
         private void displayGraphResult(@NonNull final JSONObject graphResponse) {
             logTextView.setText(graphResponse.toString());
         }
    
         /**
          * Display the error message
          */
         private void displayError(@NonNull final Exception exception) {
             logTextView.setText(exception.toString());
         }
    
         /**
          * Updates UI based on the current account.
          */
         private void updateUI() {
             if (mAccount != null) {
                 signInButton.setEnabled(false);
                 signOutButton.setEnabled(true);
                 callGraphApiInteractiveButton.setEnabled(true);
                 callGraphApiSilentButton.setEnabled(true);
                 currentUserTextView.setText(mAccount.getUsername());
             } else {
                 signInButton.setEnabled(true);
                 signOutButton.setEnabled(false);
                 callGraphApiInteractiveButton.setEnabled(false);
                 callGraphApiSilentButton.setEnabled(false);
                 currentUserTextView.setText("None");
             }
    
             deviceModeTextView.setText(mSingleAccountApp.isSharedDevice() ? "Shared" : "Non-shared");
         }
    
         /**
          * Updates UI when app sign out succeeds
          */
         private void showToastOnSignOut() {
             final String signOutText = "Signed Out.";
             currentUserTextView.setText("");
             Toast.makeText(getContext(), signOutText, Toast.LENGTH_SHORT)
                     .show();
         }
     }
    
  5. Aprire MainActivity.java e sostituire il codice con il frammento di codice seguente per gestire l'interfaccia utente.

     package com.azuresamples.msalandroidapp;
    
     import android.os.Bundle;
    
     import androidx.annotation.NonNull;
     import androidx.appcompat.app.ActionBarDrawerToggle;
     import androidx.appcompat.app.AppCompatActivity;
     import androidx.appcompat.widget.Toolbar;
     import androidx.constraintlayout.widget.ConstraintLayout;
     import androidx.core.view.GravityCompat;
    
     import android.view.MenuItem;
     import android.view.View;
    
     import androidx.drawerlayout.widget.DrawerLayout;
     import androidx.fragment.app.Fragment;
     import androidx.fragment.app.FragmentTransaction;
    
    
     import com.google.android.material.navigation.NavigationView;
    
     public class MainActivity extends AppCompatActivity
             implements NavigationView.OnNavigationItemSelectedListener,
             OnFragmentInteractionListener{
    
         enum AppFragment {
             SingleAccount
         }
    
         private AppFragment mCurrentFragment;
    
         private ConstraintLayout mContentMain;
    
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_main);
    
             mContentMain = findViewById(R.id.content_main);
    
             Toolbar toolbar = findViewById(R.id.toolbar);
             setSupportActionBar(toolbar);
             DrawerLayout drawer = findViewById(R.id.drawer_layout);
             NavigationView navigationView = findViewById(R.id.nav_view);
             ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                     this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
             drawer.addDrawerListener(toggle);
             toggle.syncState();
             navigationView.setNavigationItemSelectedListener(this);
    
             //Set default fragment
             navigationView.setCheckedItem(R.id.nav_single_account);
             setCurrentFragment(AppFragment.SingleAccount);
         }
    
         @Override
         public boolean onNavigationItemSelected(final MenuItem item) {
             final DrawerLayout drawer = findViewById(R.id.drawer_layout);
             drawer.addDrawerListener(new DrawerLayout.DrawerListener() {
                 @Override
                 public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { }
    
                 @Override
                 public void onDrawerOpened(@NonNull View drawerView) { }
    
                 @Override
                 public void onDrawerClosed(@NonNull View drawerView) {
                     // Handle navigation view item clicks here.
                     int id = item.getItemId();
    
                     if (id == R.id.nav_single_account) {
                         setCurrentFragment(AppFragment.SingleAccount);
                     }
    
    
                     drawer.removeDrawerListener(this);
                 }
    
                 @Override
                 public void onDrawerStateChanged(int newState) { }
             });
    
             drawer.closeDrawer(GravityCompat.START);
             return true;
         }
    
         private void setCurrentFragment(final AppFragment newFragment){
             if (newFragment == mCurrentFragment) {
                 return;
             }
    
             mCurrentFragment = newFragment;
             setHeaderString(mCurrentFragment);
             displayFragment(mCurrentFragment);
         }
    
         private void setHeaderString(final AppFragment fragment){
             switch (fragment) {
                 case SingleAccount:
                     getSupportActionBar().setTitle("Single Account Mode");
                     return;
    
             }
         }
    
         private void displayFragment(final AppFragment fragment){
             switch (fragment) {
                 case SingleAccount:
                     attachFragment(new com.azuresamples.msalandroidapp.SingleAccountModeFragment());
                     return;
    
             }
         }
    
         private void attachFragment(final Fragment fragment) {
             getSupportFragmentManager()
                     .beginTransaction()
                     .setTransitionStyle(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
                     .replace(mContentMain.getId(),fragment)
                     .commit();
         }
     }
    

Nota

Assicurarsi di aggiornare il nome del pacchetto in modo che corrisponda al nome del pacchetto di progetto Android.

Layout

Un layout è un file che definisce la struttura visiva e l'aspetto di un'interfaccia utente, specificando la disposizione dei componenti dell'interfaccia utente. Viene scritto in XML. Se si vuole modellare l'interfaccia utente in questa esercitazione, vengono forniti gli esempi XML seguenti:

  1. In app>src>main>res>layout>activity_main.xml. Sostituire il contenuto di activity_main.xml con il frammento di codice seguente per visualizzare pulsanti e caselle di testo:

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto"
         xmlns:tools="http://schemas.android.com/tools"
         android:id="@+id/drawer_layout"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:fitsSystemWindows="true"
         tools:openDrawer="start">
    
         <include
             layout="@layout/app_bar_main"
             android:layout_width="match_parent"
             android:layout_height="match_parent" />
    
         <com.google.android.material.navigation.NavigationView
             android:id="@+id/nav_view"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_gravity="start"
             android:fitsSystemWindows="true"
             app:headerLayout="@layout/nav_header_main"
             app:menu="@menu/activity_main_drawer" />
    
     </androidx.drawerlayout.widget.DrawerLayout>
    
  2. In app>src>main>res>layout>app_bar_main.xml. Se nella cartella non sono presenti app_bar_main.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <com.google.android.material.appbar.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                app:popupTheme="@style/AppTheme.PopupOverlay" />
    
        </com.google.android.material.appbar.AppBarLayout>
    
        <include layout="@layout/content_main" />
    
    </androidx.coordinatorlayout.widget.CoordinatorLayout>
    
  3. Nel layout>principale>>di src>dell'app>content_main.xml. Se nella cartella non sono presenti content_main.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/content_main"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:context=".MainActivity"
        tools:showIn="@layout/app_bar_main">
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  4. Nel layout>principale>>di src>dell'app>fragment_m_s_graph_request_wrapper.xml. Se nella cartella non sono presenti fragment_m_s_graph_request_wrapper.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MSGraphRequestWrapper">
    
        <!-- TODO: Update blank fragment layout -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/hello_blank_fragment" />
    
    </FrameLayout>
    
  5. Nel layout>principale>>di src>dell'app>fragment_on_interaction_listener.xml. Se nella cartella non sono presenti fragment_on_interaction_listener.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".OnFragmentInteractionListener">
    
        <!-- TODO: Update blank fragment layout -->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/hello_blank_fragment" />
    
    </FrameLayout>
    
  6. Nel layout>principale>>di src>dell'app>fragment_single_account_mode.xml. Se nella cartella non sono presenti fragment_single_account_mode.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".SingleAccountModeFragment">
    
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            tools:context=".SingleAccountModeFragment">
    
            <LinearLayout
                android:id="@+id/activity_main"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingBottom="@dimen/activity_vertical_margin">
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:paddingTop="5dp"
                    android:paddingBottom="5dp"
                    android:weightSum="10">
    
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:layout_gravity="center_vertical"
                        android:textStyle="bold"
                        android:text="Scope" />
    
                    <LinearLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        android:layout_weight="7">
    
                        <EditText
                            android:id="@+id/scope"
                            android:layout_height="wrap_content"
                            android:layout_width="match_parent"
                            android:text="user.read"
                            android:textSize="12sp" />
    
                        <TextView
                            android:layout_height="wrap_content"
                            android:layout_width="match_parent"
                            android:paddingLeft="5dp"
                            android:text="Type in scopes delimited by space"
                            android:textSize="10sp"  />
    
                    </LinearLayout>
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:paddingTop="5dp"
                    android:paddingBottom="5dp"
                    android:weightSum="10">
    
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:layout_gravity="center_vertical"
                        android:textStyle="bold"
                        android:text="MSGraph Resource URL" />
    
                    <LinearLayout
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        android:layout_weight="7">
    
                        <EditText
                            android:id="@+id/msgraph_url"
                            android:layout_height="wrap_content"
                            android:layout_width="match_parent"
                            android:textSize="12sp" />
                    </LinearLayout>
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:paddingTop="5dp"
                    android:paddingBottom="5dp"
                    android:weightSum="10">
    
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:textStyle="bold"
                        android:text="Signed-in user" />
    
                    <TextView
                        android:id="@+id/current_user"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:paddingLeft="5dp"
                        android:layout_weight="7"
                        android:text="None" />
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:paddingTop="5dp"
                    android:paddingBottom="5dp"
                    android:weightSum="10">
    
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="3"
                        android:textStyle="bold"
                        android:text="Device mode" />
    
                    <TextView
                        android:id="@+id/device_mode"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:paddingLeft="5dp"
                        android:layout_weight="7"
                        android:text="None" />
                </LinearLayout>
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:paddingTop="5dp"
                    android:paddingBottom="5dp"
                    android:weightSum="10">
    
                    <Button
                        android:id="@+id/btn_signIn"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="5"
                        android:gravity="center"
                        android:text="Sign In"/>
    
                    <Button
                        android:id="@+id/btn_removeAccount"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="5"
                        android:gravity="center"
                        android:text="Sign Out"
                        android:enabled="false"/>
                </LinearLayout>
    
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:orientation="horizontal">
    
                    <Button
                        android:id="@+id/btn_callGraphInteractively"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="5"
                        android:text="Get Graph Data Interactively"
                        android:enabled="false"/>
    
                    <Button
                        android:id="@+id/btn_callGraphSilently"
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="5"
                        android:text="Get Graph Data Silently"
                        android:enabled="false"/>
                </LinearLayout>
    
    
                <TextView
                    android:id="@+id/txt_log"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    android:layout_marginTop="20dp"
                    android:layout_weight="0.8"
                    android:text="Output goes here..." />
    
            </LinearLayout>
        </LinearLayout>
    
    </FrameLayout>
    
  7. In app>src>main>res>layout>nav_header_main.xml. Se nella cartella non sono presenti nav_header_main.xml , creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="@dimen/nav_header_height"
        android:background="@drawable/side_nav_bar"
        android:gravity="bottom"
        android:orientation="vertical"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:theme="@style/ThemeOverlay.AppCompat.Dark">
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="66dp"
            android:layout_height="72dp"
            android:contentDescription="@string/nav_header_desc"
            android:paddingTop="@dimen/nav_header_vertical_spacing"
            app:srcCompat="@drawable/microsoft_logo" />
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingTop="@dimen/nav_header_vertical_spacing"
            android:text="Azure Samples"
            android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
    
        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="MSAL Android" />
    
    </LinearLayout>
    
    
  8. Nel menu>principale>>di src>dell'app>activity_main_drawer.xml. Se non si dispone di activity_main_drawer.xml nella cartella, creare e aggiungere il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        tools:showIn="navigation_view">
        <group android:checkableBehavior="single">
            <item
                android:id="@+id/nav_single_account"
                android:icon="@drawable/ic_single_account_24dp"
                android:title="Single Account Mode" />
    
        </group>
    </menu>
    
  9. Nei valori>principali>>di src>dell'app>dimens.xml. Sostituire il contenuto di dimens.xml con il frammento di codice seguente:

    <resources>
        <dimen name="fab_margin">16dp</dimen>
        <dimen name="activity_horizontal_margin">16dp</dimen>
        <dimen name="activity_vertical_margin">16dp</dimen>
        <dimen name="nav_header_height">176dp</dimen>
        <dimen name="nav_header_vertical_spacing">8dp</dimen>
    </resources>
    
  10. In app>src>principali>valori>res>colors.xml. Sostituire il contenuto di colors.xml con il frammento di codice seguente:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="purple_200">#FFBB86FC</color>
        <color name="purple_500">#FF6200EE</color>
        <color name="purple_700">#FF3700B3</color>
        <color name="teal_200">#FF03DAC5</color>
        <color name="teal_700">#FF018786</color>
        <color name="black">#FF000000</color>
        <color name="white">#FFFFFFFF</color>
        <color name="colorPrimary">#008577</color>
        <color name="colorPrimaryDark">#00574B</color>
        <color name="colorAccent">#D81B60</color>
    </resources>
    
  11. In app>src>principali>valori>res>strings.xml. Sostituire il contenuto di strings.xml con il frammento di codice seguente:

    <resources>
        <string name="app_name">MSALAndroidapp</string>
        <string name="action_settings">Settings</string>
        <!-- Strings used for fragments for navigation -->
        <string name="first_fragment_label">First Fragment</string>
        <string name="second_fragment_label">Second Fragment</string>
        <string name="nav_header_desc">Navigation header</string>
        <string name="navigation_drawer_open">Open navigation drawer</string>
        <string name="navigation_drawer_close">Close navigation drawer</string>
        <string name="next">Next</string>
        <string name="previous">Previous</string>
    
        <string name="hello_first_fragment">Hello first fragment</string>
        <string name="hello_second_fragment">Hello second fragment. Arg: %1$s</string>
        <!-- TODO: Remove or change this placeholder text -->
        <string name="hello_blank_fragment">Hello blank fragment</string>
    </resources>
    
  12. In app>src>principali>valori>res>styles.xml. Se non si dispone di styles.xml nella cartella, creare e aggiungere il frammento di codice seguente:

    <resources>
    
    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    
    <style name="AppTheme.NoActionBar">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
    
    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
    
    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
    
    </resources>
    
  13. In app>src>principali>valori>res>themes.xml. Sostituire il contenuto di themes.xml con il frammento di codice seguente:

    <resources xmlns:tools="http://schemas.android.com/tools">
        <!-- Base application theme. -->
        <style name="Theme.MSALAndroidapp" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
            <!-- Primary brand color. -->
            <item name="colorPrimary">@color/purple_500</item>
            <item name="colorPrimaryVariant">@color/purple_700</item>
            <item name="colorOnPrimary">@color/white</item>
            <!-- Secondary brand color. -->
            <item name="colorSecondary">@color/teal_200</item>
            <item name="colorSecondaryVariant">@color/teal_700</item>
            <item name="colorOnSecondary">@color/black</item>
            <!-- Status bar color. -->
            <item name="android:statusBarColor" tools:targetApi="21">?attr/colorPrimaryVariant</item>
            <!-- Customize your theme here. -->
        </style>
    
        <style name="Theme.MSALAndroidapp.NoActionBar">
            <item name="windowActionBar">false</item>
            <item name="windowNoTitle">true</item>
        </style>
    
        <style name="Theme.MSALAndroidapp.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
    
        <style name="Theme.MSALAndroidapp.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
    </resources>
    
  14. Nell'app >src>main>res>drawable>ic_single_account_24dp.xml. Se nella cartella non sono presenti ic_single_account_24dp.xml , creare e aggiungere il frammento di codice seguente:

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
    </vector>
    
  15. Nell'app >src>main>res>drawable>side_nav_bar.xml. Se nella cartella non sono presenti side_nav_bar.xml , creare e aggiungere il frammento di codice seguente:

    <shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <gradient
        android:angle="135"
        android:centerColor="#009688"
        android:endColor="#00695C"
        android:startColor="#4DB6AC"
        android:type="linear" />
    </shape>
    
  16. In app>src>main>res>drawable. Nella cartella aggiungere un logo Microsoft png denominato microsoft_logo.png.

La dichiarazione dell'interfaccia utente in XML consente di separare la presentazione dell'app dal codice che ne controlla il comportamento. Per altre informazioni sul layout Android, vedere Layout

Testare l'app

Eseguire localmente

Compilare e distribuire l'app in un dispositivo di test o un emulatore. Dovrebbe essere possibile accedere e ottenere i token per gli account Microsoft Entra ID o Microsoft personali.

Dopo l'accesso, l'app visualizzerà i dati restituiti dall'endpoint /me di Microsoft Graph.

La prima volta che un utente accede all'app, verrà richiesto all'identità Microsoft di fornire il consenso alle autorizzazioni richieste. Alcuni tenant di Microsoft Entra hanno disabilitato il consenso dell'utente, che richiede agli amministratori di fornire il consenso per conto di tutti gli utenti. Per supportare questo scenario, sarà necessario creare un tenant personalizzato o ricevere il consenso dell'amministratore.

Pulire le risorse

Quando non è più necessario, eliminare l'oggetto app creato nel passaggio Registrare l'applicazione.

Assistenza e supporto

Se è necessaria assistenza, si vuole segnalare un problema o si vogliono ottenere informazioni sulle opzioni di supporto, vedere Assistenza e supporto per gli sviluppatori.

Passaggi successivi

Per esaminare scenari più complessi, vedere un esempio di codice funzionante completo in GitHub.

Per altre informazioni sulla creazione di app per dispositivi mobili che chiamano API Web protette nella serie di scenari in più parti, vedere: