// 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.microsoft.aad.adal;

import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Looper;
import android.os.NetworkOnMainThreadException;
import android.text.TextUtils;
import android.util.SparseArray;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.googleblog.android_developers.PRNGFixes;
import com.microsoft.aad.adal.AuthenticationRequest.UserIdentifierType;
import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
import com.microsoft.identity.common.adal.internal.util.StringExtensions;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicReference;

import static com.microsoft.identity.common.java.AuthenticationConstants.UIRequest.BROWSER_FLOW;

/**
 * ADAL context to get access token, refresh token, and lookup from cache.
 */
public class AuthenticationContext {

    private static final int EXCLUDE_INDEX = 8;

    private static final String TAG = "AuthenticationContext";

    private static final String REQUEST_ID = "requestId:";

    private Context mContext;

    private String mAuthority;

    private boolean mValidateAuthority;

    private boolean mIsAuthorityValidated;

    private ITokenCacheStore mTokenCacheStore;

    private BrokerProxy mBrokerProxy = null;

    private boolean mExtendedLifetimeEnabled = false;

    private List<String> mClientCapabilites = null;
    /**
     * Delegate map is needed to handle activity recreate without asking
     * developer to handle context instance for config changes.
     */
    private static final SparseArray<AuthenticationRequestState> DELEGATE_MAP = new SparseArray<>();

    /**
     * CorrelationId set by user or generated by ADAL.
     */
    private UUID mRequestCorrelationId = null;

    /**
     * Constructs context to use with known authority to get the token. It uses
     * default cache that stores encrypted tokens.
     *
     * @param appContext        It needs to have handle to the {@link Context} to use
     *                          the SharedPreferencesFileManager as a Default cache storage. It does not
     *                          need to be activity.
     * @param authority         Authority url to send code and token requests
     * @param validateAuthority validate authority before sending token request
     */
    public AuthenticationContext(@NonNull final Context appContext,
                                 @NonNull final String authority,
                                 final boolean validateAuthority) {
        // Fixes are required for SDK 16-18
        // The fixes need to be applied before any use of Java Cryptography
        // Architecture primitives. Default cache uses encryption
        PRNGFixes.apply();
        initialize(appContext, authority, new DefaultTokenCacheStore(appContext), validateAuthority, true);
    }

    /**
     * Constructs context to use with known authority to get the token. It uses
     * provided cache.
     *
     * @param appContext        {@link Context}
     * @param authority         Authority Url
     * @param validateAuthority true/false for validation
     * @param tokenCacheStore   Set to null if you don't want cache.
     */
    public AuthenticationContext(@NonNull final Context appContext,
                                 @NonNull final String authority,
                                 final boolean validateAuthority,
                                 @Nullable final ITokenCacheStore tokenCacheStore) {
        initialize(appContext, authority, tokenCacheStore, validateAuthority, false);
    }

    /**
     * It will verify the authority and use the given cache. If cache is null,
     * it will not use cache.
     *
     * @param appContext      {@link Context}
     * @param authority       Authority Url
     * @param tokenCacheStore Cache {@link ITokenCacheStore} used to store
     *                        tokens. Set to null if you don't want cache.
     */
    public AuthenticationContext(@NonNull final Context appContext,
                                 @NonNull final String authority,
                                 @Nullable final ITokenCacheStore tokenCacheStore) {
        initialize(appContext, authority, tokenCacheStore, true, false);
    }

    private void initialize(@NonNull final Context appContext,
                            @NonNull final String authority,
                            @Nullable final ITokenCacheStore tokenCacheStore,
                            final boolean validateAuthority,
                            final boolean defaultCache) {
        if (appContext == null) {
            throw new IllegalArgumentException("appContext");
        }

        if (authority == null) {
            throw new IllegalArgumentException("authority");
        }

        mBrokerProxy = new BrokerProxy(appContext);

        if (!defaultCache && !mBrokerProxy.canUseLocalCache(authority)) {
            throw new UnsupportedOperationException("Local cache is not supported for broker usage");
        }

        mContext = appContext;
        checkInternetPermission();
        mAuthority = extractAuthority(authority);
        mValidateAuthority = validateAuthority;

        if (null != tokenCacheStore) {
            mTokenCacheStore = new DelegatingCache(mContext, tokenCacheStore);
        }
    }

    /**
     * Returns referenced cache. You can use default cache, which uses
     * SharedPreferencesFileManager and handles synchronization by itself.
     *
     * @return ITokenCacheStore Current cache used
     */
    public ITokenCacheStore getCache() {
        return mTokenCacheStore;
    }

    /**
     * Gets if the ExtendedLifetime mode is enabled.
     *
     * @return True when ExtendedLifetime mode is enabled
     */
    public boolean getExtendedLifetimeEnabled() {
        return mExtendedLifetimeEnabled;
    }

    /**
     * The client need to set the flag to true explicitly to enable the ExtendedLifetime mode
     * The default value of flag is false.
     * ADAL will return the stale token when ExtendedLifetime mode is enabled and the server is down
     *
     * @param extendedLifetimeEnabled true if the ExtendedLifetime mode is on, false otherwise
     */
    public void setExtendedLifetimeEnabled(final boolean extendedLifetimeEnabled) {
        mExtendedLifetimeEnabled = extendedLifetimeEnabled;
    }

    /**
     * Gets authority that is used for this object of AuthenticationContext.
     *
     * @return Authority
     */
    public String getAuthority() {
        return mAuthority;
    }

    /**
     * @return True If developer turn on the authority validation, false otherwise.
     */
    public boolean getValidateAuthority() {
        return mValidateAuthority;
    }

    /**
     * Gets username for current broker user.
     *
     * @return Username
     */
    public String getBrokerUser() {
        if (mBrokerProxy != null) {
            return mBrokerProxy.getCurrentUser();
        }

        return null;
    }

    /**
     * Gets user info from broker. This should not be called on main thread.
     *
     * @return An array of {@link UserInfo} that haven been authenticated via broker(can be null).
     * @throws IOException                if the broker returned an error response that indicates that it encountered an IOException
     *                                    while communicating with the authentication server.
     * @throws AuthenticatorException     if there was an error communicating with the authenticator or if the
     *                                    authenticator returned an invalid response.
     * @throws OperationCanceledException if the request was canceled for any reason.
     */
    public UserInfo[] getBrokerUsers() throws OperationCanceledException, AuthenticatorException,
            IOException {
        return mBrokerProxy != null ? mBrokerProxy.getBrokerUsers() : null;
    }

    /**
     * Get expected redirect Uri for your app to use in broker. You need to
     * register this redirectUri in order to get token from Broker.
     *
     * @return RedirectUri string to use for broker requests.
     */
    public String getRedirectUriForBroker() {
        final String methodName = ":getRedirectUriForBroker";
        final PackageHelper packageHelper = new PackageHelper(mContext);
        final String packageName = mContext.getPackageName();

        // First available signature. Applications can be signed with multiple
        // signatures.
        final String signatureDigest = packageHelper.getSha1SignatureForPackage(packageName);
        final String redirectUri = PackageHelper.getBrokerRedirectUrl(packageName, signatureDigest);
        Logger.v(TAG + methodName, "Get expected redirect Uri. ", "Broker redirectUri:" + redirectUri + " packagename:" + packageName
                + " signatureDigest:" + signatureDigest, null);
        return redirectUri;
    }

    /**
     * acquireToken will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, it will remove
     * this refresh token from cache and start authentication.
     *
     * @param activity    required to launch authentication activity.
     * @param resource    required resource identifier.
     * @param clientId    required client identifier
     * @param redirectUri Optional. It will use package name info if not
     *                    provided.
     * @param loginHint   login hint (optional if validateAuthority == false)
     * @param callback    required
     */
    public void acquireToken(Activity activity, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable String loginHint,
                             AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, PromptBehavior.Auto, null,
                null, callback, EventStrings.ACQUIRE_TOKEN_1, wrapActivity(activity), false);
    }

    /**
     * acquireToken will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use the refresh
     * token if available. If it fails to get token with refresh token, it will
     * remove this refresh token from cache and fall back on the UI.
     *
     * @param activity             Calling activity
     * @param resource             required resource identifier.
     * @param clientId             required client identifier
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. This parameter will
     *                             be used to pre-populate the username field in the authentication
     *                             form. Please note that the end user can still edit the username
     *                             field and authenticate as a different user.
     * @param extraQueryParameters Optional. This parameter will be appended as
     *                             is to the query string in the HTTP authentication request to
     *                             the authority. The parameter can be null.
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable String loginHint, @Nullable String extraQueryParameters,
                             AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, PromptBehavior.Auto, extraQueryParameters,
                null, callback, EventStrings.ACQUIRE_TOKEN_2, wrapActivity(activity), false);
    }

    /**
     * acquireToken will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If {@link PromptBehavior} is AUTO, it will remove this
     * refresh token from cache and fall back on the UI. Default is AUTO. if
     * {@link PromptBehavior} is Always, it will display prompt screen.
     *
     * @param activity    Calling activity
     * @param resource    required resource identifier.
     * @param clientId    required client identifier.
     * @param redirectUri Optional. It will use packagename and provided suffix
     *                    for this.
     * @param prompt      Optional. {@link PromptBehavior} added as query parameter
     *                    to authorization url
     * @param callback    required {@link AuthenticationCallback} object for async
     *                    call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable PromptBehavior prompt,
                             AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, null, prompt, null,
                null, callback, EventStrings.ACQUIRE_TOKEN_3, wrapActivity(activity), false);
    }

    /**
     * acquireToken will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI if activitycontext is not null.
     * Default is AUTO.
     *
     * @param activity             Calling activity
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable PromptBehavior prompt, @Nullable String extraQueryParameters,
                             AuthenticationCallback<AuthenticationResult> callback) {
        acquireToken(resource, clientId, redirectUri, null, prompt, extraQueryParameters,
                null, callback, EventStrings.ACQUIRE_TOKEN_4, wrapActivity(activity), false);
    }

    /**
     * acquireToken will start interactive flow if needed. It checks the cache
     * to return existing result if not expired. It tries to use refresh token
     * if available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI if activitycontext is not null.
     * Default is AUTO.
     *
     * @param activity             Calling activity
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(Activity activity, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable String loginHint, @Nullable PromptBehavior prompt,
                             @Nullable String extraQueryParameters, AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                null, callback, EventStrings.ACQUIRE_TOKEN_5, wrapActivity(activity), false);
    }

    /**
     * acquireToken will start an interactive auth flow to acquire new tokens
     * with the requested claims. Bypasses token cache if promptbehavior is not AUTO or claims are passed.
     *
     * @param activity             Calling activity
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param claims               Optional. The claims challenge returned from middle tier service, will be added as query string
     *                             to authorize endpoint.
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(final Activity activity, final String resource, final String clientId, @Nullable String redirectUri,
                             @Nullable final String loginHint, @Nullable final PromptBehavior prompt, @Nullable String extraQueryParameters,
                             @Nullable final String claims, final AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                claims, callback, EventStrings.ACQUIRE_TOKEN_8, wrapActivity(activity), false);
    }

    /**
     * It will start interactive flow if needed. It checks the cache to return
     * existing result if not expired. It tries to use refresh token if
     * available. If it fails to get token with refresh token, behavior will
     * depend on options. If promptbehavior is AUTO, it will remove this refresh
     * token from cache and fall back on the UI. Default is AUTO.
     *
     * @param fragment             It accepts both type of fragments.
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(IWindowComponent fragment, String resource, String clientId,
                             @Nullable String redirectUri, @Nullable String loginHint, @Nullable PromptBehavior prompt,
                             @Nullable String extraQueryParameters, AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                null, callback, EventStrings.ACQUIRE_TOKEN_6, fragment, false);

    }

    /**
     * acquireToken will start an interactive auth flow to acquire new tokens
     * with the requested claims. Bypasses token cache if promptbehavior is not AUTO or claims are passed.
     *
     * @param fragment             It accepts both type of fragments.
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param claims               Optional. The claims challenge returned from middle tier service, will be added as query string
     *                             to authorize endpoint.
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(final IWindowComponent fragment, final String resource, final String clientId, @Nullable String redirectUri,
                             @Nullable final String loginHint, @Nullable final PromptBehavior prompt, @Nullable String extraQueryParameters,
                             @Nullable final String claims, final AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                claims, callback, EventStrings.ACQUIRE_TOKEN_9, fragment, false);
    }

    /**
     * acquireToken will authorize an end user to call the specified resource.
     * The access token returned from the STS will be sent to the {@link AuthenticationCallback}
     * and can be used to call the specified protected resource.
     * By default, acquireToken will attempt to fulfill the request silently, and
     * perform an interactive login if needed or explicitly specified in
     * the request. This overload uses an
     * [AlertDialog](https://developer.android.com/guide/topics/ui/dialogs)
     * when user interaction is required. This overload does not support any flow
     * requiring a
     * [token broker](https://github.com/AzureAD/azure-activedirectory-library-for-android/wiki/Broker).
     *
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(String resource, String clientId, @Nullable String redirectUri,
                             @Nullable String loginHint, @Nullable PromptBehavior prompt, @Nullable String extraQueryParameters,
                             AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                null, callback, EventStrings.ACQUIRE_TOKEN_7, null, true);
    }

    /**
     * acquireToken will authorize an end user to call the specified resource.
     * The access token returned from the STS will be sent to the {@link AuthenticationCallback}
     * and can be used to call the specified protected resource.
     * Bypasses token cache if @param prompt is not AUTO or claims are passed.
     * This overload uses an
     * [AlertDialog](https://developer.android.com/guide/topics/ui/dialogs)
     * when user interaction is required. This overload does not support any flow
     * requiring a
     * [token broker](https://github.com/AzureAD/azure-activedirectory-library-for-android/wiki/Broker).
     *
     * @param resource             required resource identifier.
     * @param clientId             required client identifier.
     * @param redirectUri          Optional. It will use packagename and provided suffix
     *                             for this.
     * @param loginHint            Optional if validateAuthority == null. It is used for cache and as a loginhint at
     *                             authentication.
     * @param prompt               Optional. added as query parameter to authorization url
     * @param extraQueryParameters Optional. added to authorization url
     * @param claims               Optional. The claims challenge returned from middle tier service, will be added as query string
     *                             to authorize endpoint.
     * @param callback             required {@link AuthenticationCallback} object for async
     *                             call.
     */
    public void acquireToken(final String resource, final String clientId, @Nullable String redirectUri,
                             @Nullable final String loginHint, @Nullable final PromptBehavior prompt, @Nullable String extraQueryParameters,
                             @Nullable final String claims, final AuthenticationCallback<AuthenticationResult> callback) {

        acquireToken(resource, clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                claims, callback, EventStrings.ACQUIRE_TOKEN_10, null, false);

    }

    private void acquireToken(final String resource, final String clientId, @Nullable String redirectUri,
                              @Nullable final String loginHint, @Nullable final PromptBehavior prompt,
                              @Nullable String extraQueryParameters, @Nullable final String claims,
                              final AuthenticationCallback<AuthenticationResult> callback, String apiEventString,
                              final IWindowComponent fragment, final boolean useDialog) {

        throwIfClaimsInBothExtraQpAndClaimsParameter(claims, extraQueryParameters);

        try {
            validateClaims(claims);
        } catch (AuthenticationException e) {
            callback.onError(e);
            return;
        }

        if (checkPreRequirements(resource, clientId, callback) && checkADFSValidationRequirements(loginHint, callback)) {
            redirectUri = getRedirectUri(redirectUri);
            final String requestId = Telemetry.registerNewRequest();
            final APIEvent apiEvent = createApiEvent(mContext, clientId, requestId, apiEventString);
            apiEvent.setPromptBehavior(prompt);

            final AuthenticationRequest request = new AuthenticationRequest(mAuthority, resource,
                    clientId, redirectUri, loginHint, prompt, extraQueryParameters,
                    getRequestCorrelationId(), getExtendedLifetimeEnabled(), claims);
            request.setTelemetryRequestId(requestId);
            setAppInfoToRequest(request);
            request.setClientCapabilities(mClientCapabilites);

            if (!StringExtensions.isNullOrBlank(loginHint)) {
                apiEvent.setLoginHint(loginHint);
                request.setUserIdentifierType(UserIdentifierType.LoginHint);
            }

            createAcquireTokenRequest(apiEvent).acquireToken(fragment, useDialog, request, callback);
        }

    }

    /**
     * This is sync function. It will first look at the cache and automatically
     * checks for the token expiration. Additionally, if no suitable access
     * token is found in the cache, but refresh token is available, the function
     * will use the refresh token automatically. This method will not show UI
     * for the user. If prompt is needed, the method will return an exception
     *
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param userId   UserID obtained from
     *                 {@link AuthenticationResult #getUserInfo()}
     * @return A {@link Future} object representing the
     * {@link AuthenticationResult} of the call. It contains Access
     * Token,the Access Token's expiration time, Refresh token, and
     * {@link UserInfo}.
     * @throws AuthenticationException If silent request fails to get the token back.
     * @throws InterruptedException    If the main thread is interrupted before or during the activity.
     */
    public AuthenticationResult acquireTokenSilentSync(String resource, String clientId, String userId)
            throws AuthenticationException, InterruptedException {
        return acquireTokenSilentSync(resource, clientId, userId, false, null, EventStrings.ACQUIRE_TOKEN_SILENT_SYNC);
    }

    /**
     * This is sync function. It will first look at the cache and automatically
     * checks for the token expiration. Additionally, if no suitable access
     * token is found in the cache, but refresh token is available, the function
     * will use the refresh token automatically. This method will not show UI
     * for the user. If prompt is needed, the method will return an exception
     *
     * @param resource required resource identifier.
     * @param clientId required client identifier.
     * @param userId   UserID obtained from
     *                 {@link AuthenticationResult #getUserInfo()}
     * @param claims   Optional. The claims challenge returned from middle tier service, will be added as query string
     *                 to authorize endpoint.
     * @return A {@link Future} object representing the
     * {@link AuthenticationResult} of the call. It contains Access
     * Token,the Access Token's expiration time, Refresh token, and
     * {@link UserInfo}.
     * @throws AuthenticationException If silent request fails to get the token back.
     * @throws InterruptedException    If the main thread is interrupted before or during the activity.
     */
    public AuthenticationResult acquireTokenSilentSync(String resource, String clientId, String userId, @Nullable String claims)
            throws AuthenticationException, InterruptedException {
        return acquireTokenSilentSync(resource, clientId, userId, false, claims, EventStrings.ACQUIRE_TOKEN_SILENT_SYNC_CLAIMS_CHALLENGE);
    }

    /**
