diff --git a/app/build.gradle b/app/build.gradle index b2c6f0d..8e1eb01 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,9 @@ android { versionCode 1 versionName "1.0" } + packagingOptions { + pickFirst 'META-INF/*' + } signingConfigs { debug { storeFile file("../gradle/debug.keystore") @@ -78,6 +81,10 @@ android { setIgnore(true); } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { @@ -87,6 +94,9 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:$rootProject.ext.constraintLayoutVersion" implementation "androidx.legacy:legacy-support-v4:$rootProject.ext.legacySupportV4Version" implementation 'com.android.volley:volley:1.1.1' + implementation ('com.microsoft.graph:microsoft-graph:3.1.0') { + exclude group: 'javax.activation' + } if (findProject(':msal') != null) { // For developer team only. diff --git a/app/src/main/java/com/azuresamples/msalandroidapp/MSGraphRequestWrapper.java b/app/src/main/java/com/azuresamples/msalandroidapp/MSGraphRequestWrapper.java index 12e8470..5736015 100644 --- a/app/src/main/java/com/azuresamples/msalandroidapp/MSGraphRequestWrapper.java +++ b/app/src/main/java/com/azuresamples/msalandroidapp/MSGraphRequestWrapper.java @@ -24,9 +24,11 @@ package com.azuresamples.msalandroidapp; import android.content.Context; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import com.android.volley.DefaultRetryPolicy; import com.android.volley.Request; @@ -34,61 +36,64 @@ import com.android.volley.Response; import com.android.volley.toolbox.JsonObjectRequest; import com.android.volley.toolbox.Volley; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.microsoft.graph.requests.GraphServiceClient; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; 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/"; + public static final String MS_GRAPH_V1_ENDPOINT = MS_GRAPH_ROOT_ENDPOINT + "v1.0"; + public static final String MS_GRAPH_BETA_ENDPOINT = MS_GRAPH_ROOT_ENDPOINT + "beta"; /** - * Use Volley to make an HTTP request with + * Use Graph SDK 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 responseListener, - @NonNull final Response.ErrorListener errorListener) { - Log.d(TAG, "Starting volley request to graph"); + @RequiresApi(api = Build.VERSION_CODES.N) + @SuppressWarnings("unchecked") + public static CompletableFuture callGraphAPI(@NonNull final String graphResourceUrl, + @NonNull final String accessToken) { + Log.d(TAG, "Starting SDK request to graph"); /* Make sure we have a token to send to graph */ if (accessToken == null || accessToken.length() == 0) { - return; + return CompletableFuture.completedFuture(null); } - RequestQueue queue = Volley.newRequestQueue(context); - JSONObject parameters = new JSONObject(); + StaticTokenAuthProvider authProvider = + new StaticTokenAuthProvider(accessToken); - try { - parameters.put("key", "value"); - } catch (Exception e) { - Log.d(TAG, "Failed to put parameters: " + e.toString()); - } + GraphServiceClient graphClient = GraphServiceClient.builder() + .authenticationProvider(authProvider) + .buildClient(); - JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, graphResourceUrl, - parameters, responseListener, errorListener) { - @Override - public Map getHeaders() { - Map headers = new HashMap<>(); - headers.put("Authorization", "Bearer " + accessToken); - return headers; - } - }; + String relativeUrl = ""; - Log.d(TAG, "Adding HTTP GET to Queue, Request: " + request.toString()); + if (graphResourceUrl.toLowerCase().contains(MS_GRAPH_V1_ENDPOINT)) { + relativeUrl = graphResourceUrl.substring(MS_GRAPH_V1_ENDPOINT.length()); + } else if (graphResourceUrl.toLowerCase().contains(MS_GRAPH_BETA_ENDPOINT)) { + relativeUrl = graphResourceUrl.substring(MS_GRAPH_BETA_ENDPOINT.length()); + } - request.setRetryPolicy(new DefaultRetryPolicy( - 3000, - DefaultRetryPolicy.DEFAULT_MAX_RETRIES, - DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); - queue.add(request); + // The customRequest builder allows you to call any relative URL + // (like "/me") and get back a generic object + // For an example that uses the SDK's fluent API to return strongly-typed objects, + // see https://github.com/microsoftgraph/msgraph-training-android + // or https://docs.microsoft.com/en-us/graph/tutorials/android + return graphClient.customRequest(relativeUrl) + .buildRequest() + .getAsync() + .thenApply(response -> response.toString()); } } diff --git a/app/src/main/java/com/azuresamples/msalandroidapp/MultipleAccountModeFragment.java b/app/src/main/java/com/azuresamples/msalandroidapp/MultipleAccountModeFragment.java index d049de0..f843e30 100644 --- a/app/src/main/java/com/azuresamples/msalandroidapp/MultipleAccountModeFragment.java +++ b/app/src/main/java/com/azuresamples/msalandroidapp/MultipleAccountModeFragment.java @@ -23,9 +23,11 @@ package com.azuresamples.msalandroidapp; +import android.os.Build; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import android.util.Log; @@ -230,6 +232,7 @@ public void onError(MsalException exception) { private SilentAuthenticationCallback getAuthSilentCallback() { return new SilentAuthenticationCallback() { + @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onSuccess(IAuthenticationResult authenticationResult) { Log.d(TAG, "Successfully authenticated"); @@ -304,26 +307,20 @@ public void onCancel() { * If you're developing an app for sovereign cloud users, please change the Microsoft Graph Resource URL accordingly. * https://docs.microsoft.com/en-us/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints */ + @RequiresApi(api = Build.VERSION_CODES.N) private void callGraphAPI(final IAuthenticationResult authenticationResult) { - MSGraphRequestWrapper.callGraphAPIUsingVolley( - getContext(), - graphResourceTextView.getText().toString(), - authenticationResult.getAccessToken(), - new Response.Listener() { - @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); - } - }); + MSGraphRequestWrapper.callGraphAPI( + graphResourceTextView.getText().toString(), + authenticationResult.getAccessToken()) + .thenAccept(json -> { + Log.d(TAG, "Response: " + json); + displayGraphResult(json); + }) + .exceptionally(exception -> { + Log.d(TAG, "Error: " + exception.toString()); + displayError(exception); + return null; + }); } // @@ -338,14 +335,14 @@ public void onErrorResponse(VolleyError error) { /** * Display the graph response */ - private void displayGraphResult(@NonNull final JSONObject graphResponse) { - logTextView.setText(graphResponse.toString()); + private void displayGraphResult(@NonNull final String graphResponse) { + logTextView.setText(graphResponse); } /** * Display the error message */ - private void displayError(@NonNull final Exception exception) { + private void displayError(@NonNull final Throwable exception) { logTextView.setText(exception.toString()); } diff --git a/app/src/main/java/com/azuresamples/msalandroidapp/SingleAccountModeFragment.java b/app/src/main/java/com/azuresamples/msalandroidapp/SingleAccountModeFragment.java index 1b4e7ab..d254d1d 100644 --- a/app/src/main/java/com/azuresamples/msalandroidapp/SingleAccountModeFragment.java +++ b/app/src/main/java/com/azuresamples/msalandroidapp/SingleAccountModeFragment.java @@ -23,10 +23,12 @@ package com.azuresamples.msalandroidapp; +import android.os.Build; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.fragment.app.Fragment; import android.util.Log; @@ -257,6 +259,7 @@ public void onError(@NonNull MsalException exception) { private SilentAuthenticationCallback getAuthSilentCallback() { return new SilentAuthenticationCallback() { + @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onSuccess(IAuthenticationResult authenticationResult) { Log.d(TAG, "Successfully authenticated"); @@ -290,6 +293,7 @@ public void onError(MsalException exception) { private AuthenticationCallback getAuthInteractiveCallback() { return new AuthenticationCallback() { + @RequiresApi(api = Build.VERSION_CODES.N) @Override public void onSuccess(IAuthenticationResult authenticationResult) { /* Successfully got a token, use it to call a protected resource - MSGraph */ @@ -328,26 +332,20 @@ public void onCancel() { /** * Make an HTTP request to obtain MSGraph data */ + @RequiresApi(api = Build.VERSION_CODES.N) private void callGraphAPI(final IAuthenticationResult authenticationResult) { - MSGraphRequestWrapper.callGraphAPIUsingVolley( - getContext(), - graphResourceTextView.getText().toString(), - authenticationResult.getAccessToken(), - new Response.Listener() { - @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); - } - }); + MSGraphRequestWrapper.callGraphAPI( + graphResourceTextView.getText().toString(), + authenticationResult.getAccessToken()) + .thenAccept(json -> { + Log.d(TAG, "Response: " + json); + displayGraphResult(json); + }) + .exceptionally(exception -> { + Log.d(TAG, "Error: " + exception.toString()); + displayError(exception); + return null; + }); } // @@ -362,14 +360,14 @@ public void onErrorResponse(VolleyError error) { /** * Display the graph response */ - private void displayGraphResult(@NonNull final JSONObject graphResponse) { - logTextView.setText(graphResponse.toString()); + private void displayGraphResult(@NonNull final String graphResponse) { + logTextView.setText(graphResponse); } /** * Display the error message */ - private void displayError(@NonNull final Exception exception) { + private void displayError(@NonNull final Throwable exception) { logTextView.setText(exception.toString()); } diff --git a/app/src/main/java/com/azuresamples/msalandroidapp/StaticTokenAuthProvider.java b/app/src/main/java/com/azuresamples/msalandroidapp/StaticTokenAuthProvider.java new file mode 100644 index 0000000..d6ae0cb --- /dev/null +++ b/app/src/main/java/com/azuresamples/msalandroidapp/StaticTokenAuthProvider.java @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. +// All rights reserved. +// +// This code is licensed under the MIT License. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package com.azuresamples.msalandroidapp; + +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import com.microsoft.graph.authentication.BaseAuthenticationProvider; + +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +import javax.annotation.Nonnull; + +/** + * Implementation sample for a simplistic authentication provider + *

+ * The Microsoft Graph SDK client accepts an authentication provider + * that is responsible for providing access tokens to authenticate API + * calls. Because this sample focuses on acquiring tokens manually, this + * provider simply returns the provided token. + *

+ * Real-world providers would take advantage of MSAL's caching abilities + * to always get a valid token, refresh the token when expired, etc. + */ +public class StaticTokenAuthProvider extends BaseAuthenticationProvider { + + private final String mAccessToken; + + public StaticTokenAuthProvider(String accessToken) { + mAccessToken = accessToken; + } + + @RequiresApi(api = Build.VERSION_CODES.N) + @Nonnull + @Override + public CompletableFuture getAuthorizationTokenAsync(@Nonnull URL requestUrl) { + if (shouldAuthenticateRequestWithUrl(requestUrl)) { + return CompletableFuture.completedFuture(mAccessToken); + } + return CompletableFuture.completedFuture(null); + } +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 609ce37..b7af54f 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -1,14 +1,14 @@ // Variables for entire project ext { // SDK - minSdkVersion = 16 + minSdkVersion = 26 automationAppMinSDKVersion = 21 targetSdkVersion = 28 compileSdkVersion = 28 buildToolsVersion = "28.0.3" // Plugins - gradleVersion = '4.0.2' + gradleVersion = '4.1.3' androidMavenGradlePluginVersion = "1.4.1" // Libraries