() {
                            @Override
                            public void onResult(Void result) {
                                callback.onResult(result);
                            }
                            @Override
                            public void onError(Exception e) {
                                callback.onError(e);
                            }
                        });
                    }
                    @Override
                    public void onFailure(Exception exception) {
                        callback.onError(exception);
                    }
                });
            }
        };
    }
    /**
     * Federate tokens from custom identity providers into Cognito Identity Pool by providing the
     * logins key and token
     * 
     * The logins key can be specified with {@link IdentityProvider#AMAZON#toString()}
     *
     * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com
     * @param token       the JWT token vended by the third-party
     */
    @AnyThread
    public void federatedSignIn(final String providerKey,
                                final String token,
                                final Callback callback) {
        InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_federatedSignIn(providerKey, token, null, internalCallback, true));
    }
    /**
     * Federate tokens from custom identity providers into Cognito Identity Pool by providing the
     * logins key and token
     * 
     * The logins key can be specified with {@link IdentityProvider#AMAZON}
     *
     * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com
     * @param token       the JWT token vended by the third-party
     */
    @WorkerThread
    public UserStateDetails federatedSignIn(final String providerKey, final String token) throws Exception {
        InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_federatedSignIn(providerKey, token, null, internalCallback, true));
    }
    /**
     * Federate tokens from custom identity providers by providing the
     * logins key and token
     * 
     * The logins key can be specified with {@link IdentityProvider#AMAZON#toString()}
     *
     * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com
     * @param token       the JWT token vended by the third-party
     */
    @AnyThread
    public void federatedSignIn(final String providerKey,
                                final String token,
                                final FederatedSignInOptions options,
                                final Callback callback) {
        InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_federatedSignIn(providerKey, token, options, internalCallback, true));
    }
    /**
     * Federate tokens from custom identity providers by providing the
     * logins key and token
     * 
     * The logins key can be specified with {@link IdentityProvider#AMAZON}
     *
     * @param providerKey Custom provider key i.e. Google sign-in's key is accounts.google.com
     * @param token       the JWT token vended by the third-party
     */
    @WorkerThread
    public UserStateDetails federatedSignIn(final String providerKey,
                                final String token,
                                final FederatedSignInOptions options) throws Exception {
        InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_federatedSignIn(providerKey, token, options, internalCallback, true));
    }
    protected void federatedSignInWithoutAssigningState(final String providerKey, final String token) throws Exception {
        InternalCallback internalCallback = new InternalCallback();
        internalCallback.await(_federatedSignIn(providerKey, token, null, internalCallback, false));
    }
    protected void federatedSignInWithoutAssigningState(final String providerKey,
                                                        final String token,
                                                        final Callback callback) {
        InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_federatedSignIn(providerKey, token, null, internalCallback, false));
    }
    private Runnable _federatedSignIn(final String providerKey,
                                      final String token,
                                      final FederatedSignInOptions options,
                                      final Callback callback,
                                      final boolean assignState) {
        final Map loginsMap = new HashMap();
        mStore.set(SIGN_IN_MODE, SignInMode.FEDERATED_SIGN_IN.toString());
        try {
            loginsMap.put(providerKey, token);
            Log.d(TAG, String.format("_federatedSignIn: Putting provider and token in store"));
            HashMap details = new HashMap();
            details.put(PROVIDER_KEY, providerKey);
            details.put(TOKEN_KEY, token);
            details.put(FEDERATION_ENABLED_KEY, "true");
            if (IdentityProvider.DEVELOPER.equals(providerKey)) {
                if (options == null) {
                    callback.onError(new Exception("Developer authenticated identities require the" +
                            "identity id to be specified in FederatedSignInOptions"));
                }
                details.put(IDENTITY_ID_KEY, options.getCognitoIdentityId());
            }
            if (options != null && !StringUtils.isBlank(options.getCustomRoleARN())) {
                details.put(CUSTOM_ROLE_ARN_KEY, options.getCustomRoleARN());
            }
            mStore.set(details);
        } catch (Exception e) {
            callback.onError(e);
        }
        return new Runnable() {
            @Override
            public void run() {
                try {
                    if (cognitoIdentity == null) {
                        callback.onError(new Exception("Federation is not enabled, " +
                                "please check if you have CognitoIdentity configured."));
                        return;
                    }
                    if (!token.equals(mFederatedLoginsMap.get(providerKey))) {
                        cognitoIdentity.clear();
                        cognitoIdentity.setLogins(loginsMap);
                    }
                    UserStateDetails userStateDetails = getUserStateDetails(true);
                    federateWithCognitoIdentity(providerKey, token);
                    callback.onResult(userStateDetails);
                    end(userStateDetails);
                } catch (final Exception exception) {
                    HashMap details = new HashMap();
                    details.put(PROVIDER_KEY, null);
                    details.put(TOKEN_KEY, null);
                    details.put(FEDERATION_ENABLED_KEY, null);
                    details.put(IDENTITY_ID_KEY, null);
                    details.put(CUSTOM_ROLE_ARN_KEY, null);
                    mStore.set(details);
                    callback.onError(new RuntimeException("Error in federating the token.", exception));
                    return;
                }
            }
            private void end(final UserStateDetails details) {
                if (assignState) {
                    setUserState(details);
                }
            }
        };
    }
    protected void federateWithCognitoIdentity(final String providerKey,
                                               final String token) {
        synchronized (federateWithCognitoIdentityLockObject) {
            if (!hasFederatedToken(providerKey, token)) {
                if (IdentityProvider.DEVELOPER.equals(providerKey)) {
                    provider.setDeveloperAuthenticated(mStore.get(IDENTITY_ID_KEY), token);
                } else {
                    provider.setNotDeveloperAuthenticated();
                }
                final String customRoleArn = mStore.get(CUSTOM_ROLE_ARN_KEY);
                if (!StringUtils.isBlank(customRoleArn)) {
                    cognitoIdentity.setCustomRoleArn(customRoleArn);
                }
                HashMap logins = new HashMap();
                logins.put(providerKey, token);
                cognitoIdentity.setLogins(logins);
                cognitoIdentity.refresh();
                // Ensure cognitoIdentityId and credentials can be retrieved.
                mStore.set(IDENTITY_ID_KEY, cognitoIdentity.getIdentityId());
                mFederatedLoginsMap = cognitoIdentity.getLogins();
            }
        }
    }
    /**
     * Returns the tokens obtained from Cognito Userpools sign-in.
     * Federated sign-in tokens are not supported.
     *
     * @return tokens from Cognito Userpools
     * @throws Exception when the tokens cannot be retrieved successfully.
     */
    @WorkerThread
    public Tokens getTokens() throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_getTokens(internalCallback, false));
    }
    /**
     * Returns the tokens obtained from Cognito Userpools sign-in.
     * Federated sign-in tokens are not supported.
     *
     * @return tokens from Cognito Userpools
     * @throws Exception when the tokens cannot be retrieved successfully.
     */
    @AnyThread
    public void getTokens(final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_getTokens(internalCallback, false));
    }
    protected Tokens getTokens(boolean waitForSignIn) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_getTokens(internalCallback, waitForSignIn));
    }
    private Runnable _getTokens(final Callback callback, final boolean waitForSignIn) {
        return new Runnable() {
            @Override
            public void run() {
                String providerKey = getSignInDetailsMap().get(PROVIDER_KEY);
                if (providerKey == null) {
                } else if (!userpoolsLoginKey.equals(providerKey)) {
                    callback.onError(new Exception("getTokens does not support retrieving tokens for federated sign-in"));
                    return;
                }
                if (waitForSignIn) {
                    if (!waitForSignIn()) {
                        callback.onError(new Exception("getTokens does not support retrieving tokens while signed-out"));
                        return;
                    }
                }
                if (!isUserpoolsSignedIn()) {
                    callback.onError(new Exception("You must be signed-in with Cognito Userpools to be able to use getTokens"));
                }
                if (getSignInMode().equals(SignInMode.HOSTED_UI)) {
                    _getHostedUITokens(callback);
                    return;
                } else if (getSignInMode().equals(SignInMode.OAUTH2)) {
                    callback.onError(new Exception("Tokens are not supported for OAuth2"));
                    return;
                }
                try {
                    userpool.getCurrentUser().getSession(
                        Collections.emptyMap(),
                        new AuthenticationHandler() {
                            @Override
                            public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) {
                                try {
                                    mCognitoUserSession = userSession;
                                    callback.onResult(new Tokens(
                                            userSession.getAccessToken().getJWTToken(),
                                            userSession.getIdToken().getJWTToken(),
                                            userSession.getRefreshToken().getToken()
                                    ));
                                } catch (Exception e) {
                                    callback.onError(e);
                                }
                            }
                            @Override
                            public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) {
                                signalTokensNotAvailable(null);
                            }
                            @Override
                            public void getMFACode(MultiFactorAuthenticationContinuation continuation) {
                                signalTokensNotAvailable(null);
                            }
                            @Override
                            public void authenticationChallenge(ChallengeContinuation continuation) {
                                signalTokensNotAvailable(null);
                            }
                            @Override
                            public void onFailure(Exception exception) {
                                signalTokensNotAvailable(exception);
                            }
                            private void signalTokensNotAvailable(final Exception e) {
                                Log.w(TAG, "signalTokensNotAvailable");
                                callback.onError(new Exception("No cached session.", e));
                            }
                        }
                    );
                } catch (Exception e) {
                    callback.onError(e);
                }
            }
        };
    }
    @AnyThread
    private Tokens getHostedUITokens() throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(() -> _getHostedUITokens(internalCallback));
    }
    private void _getHostedUITokens(final Callback callback) {
        hostedUI = hostedUI.getCurrentUser();
        hostedUI.setAuthHandler(new AuthHandler() {
            @Override
            public void onSuccess(AuthUserSession session) {
                callback.onResult(new Tokens(
                        session.getAccessToken().getJWTToken(),
                        session.getIdToken().getJWTToken(),
                        session.getRefreshToken().getToken()
                ));
            }
            @Override
            public void onSignout() {
                callback.onError(new Exception("No cached session."));
            }
            @Override
            public void onFailure(Exception e) {
                callback.onError(new Exception("No cached session.", e));
            }
        });
        hostedUI.getSessionWithoutWebUI();
    }
    /**
     * Sign-up users. The {@link SignUpResult} will contain next steps if necessary.
     * Call {@link #confirmSignUp(String, String, Callback)} with the necessary next
     * step code obtained from user.
     *
     * @param username username/email address/handle
     * @param password user's password
     * @param userAttributes attributes associated with user
     * @param validationData optional, set of data to validate the sign-up request
     * @param clientMetadata meta data to be passed to the lambdas invoked by sign up operation.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 SignUp operation
     */
    @AnyThread
    public void signUp(final String username,
                       final String password,
                       final Map userAttributes,
                       final Map validationData,
                       final Map clientMetadata,
                       final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_signUp(username, password, userAttributes, validationData,
                clientMetadata, internalCallback));
    }
    /**
     * Sign-up users. The {@link SignUpResult} will contain next steps if necessary.
     * Call {@link #confirmSignUp(String, String)} with the necessary next step code obtained from user.
     *
     * @param username username/email address/handle
     * @param password user's password
     * @param userAttributes attributes associated with user
     * @param validationData optional, set of data to validate the sign-up request
     * @param clientMetadata meta data to be passed to the lambdas invoked by sign up operation.
     * @return result of the operation, potentially with next steps
     * @throws Exception if there is any error generated by the client
     */
    @WorkerThread
    public SignUpResult signUp(final String username,
                               final String password,
                               final Map userAttributes,
                               final Map clientMetadata,
                               final Map validationData) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_signUp(username, password, userAttributes, validationData,
                clientMetadata, internalCallback));
    }
    /**
     * Sign-up users. The {@link SignUpResult} will contain next steps if necessary.
     * Call {@link #confirmSignUp(String, String, Callback)} with the necessary next
     * step code obtained from user.
     *
     * @param username username/email address/handle
     * @param password user's password
     * @param userAttributes attributes associated with user
     * @param validationData optional, set of data to validate the sign-up request
     * @param callback callback will be invoked to notify the success or failure of the
     *                 SignUp operation
     */
    @AnyThread
    public void signUp(final String username,
                       final String password,
                       final Map userAttributes,
                       final Map validationData,
                       final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_signUp(username, password, userAttributes, validationData,
                Collections.emptyMap(), internalCallback));
    }
    /**
     * Sign-up users. The {@link SignUpResult} will contain next steps if necessary.
     * Call {@link #confirmSignUp(String, String)} with the necessary next step code obtained from user.
     *
     * @param username username/email address/handle
     * @param password user's password
     * @param userAttributes attributes associated with user
     * @param validationData optional, set of data to validate the sign-up request
     * @return result of the operation, potentially with next steps
     * @throws Exception if there is any error generated by the client
     */
    @WorkerThread
    public SignUpResult signUp(final String username,
                               final String password,
                               final Map userAttributes,
                               final Map validationData) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_signUp(username, password, userAttributes, validationData,
                Collections.emptyMap(), internalCallback));
    }
    private Runnable _signUp(final String username,
                             final String password,
                             final Map userAttributes,
                             final Map validationData,
                             final Map clientMetadata,
                             final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                final CognitoUserAttributes cognitoUserAttr = new CognitoUserAttributes();
                for (final String key : userAttributes.keySet()) {
                    cognitoUserAttr.addAttribute(key, userAttributes.get(key));
                }
                userpool.signUp(username, password, cognitoUserAttr, validationData, clientMetadata, new SignUpHandler() {
                    @Override
                    public void onSuccess(final CognitoUser user,
                                          final com.amazonaws.services.cognitoidentityprovider.model.SignUpResult signUpResult) {
                        signUpUser = user;
                        if (signUpResult == null) {
                            callback.onError(new Exception("SignUpResult received is null"));
                            return;
                        }
                        // When the user is confirmed, Cognito does not send CognitoUserCodeDeliveryDetails
                        // and it appears to be null when the SignUpResult is unmarshalled. Extract the
                        // CognitoUserCodeDeliveryDetails only when the user is not confirmed.
                        if (signUpResult.getCodeDeliveryDetails() == null) {
                            callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(),
                                    null,
                                    signUpResult.getUserSub()));
                        } else {
                            UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails(
                                    signUpResult.getCodeDeliveryDetails().getDestination(),
                                    signUpResult.getCodeDeliveryDetails().getDeliveryMedium(),
                                    signUpResult.getCodeDeliveryDetails().getAttributeName());
                            callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(),
                                            userCodeDeliveryDetails,
                                            signUpResult.getUserSub()));
                        }
                    }
                    @Override
                    public void onFailure(Exception exception) {
                        callback.onError(exception);
                    }
                });
            }
        };
    }
    /**
     * Confirm the sign-up request with follow-up information
     *
     * @param username username/email address/handle of the user who is signing up
     * @param signUpChallengeResponse response to the signUp challenge posted
     * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation.
     * @param callback the callback will be invoked to notify the success or
     *                 failure of the confirmSignUp operation
     */
    @AnyThread
    public void confirmSignUp(final String username,
                              final String signUpChallengeResponse,
                              final Map clientMetadata,
                              final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_confirmSignUp(username, signUpChallengeResponse,
                clientMetadata, internalCallback));
    }
    /**
     * Confirm the sign-up request with follow-up information
     *
     * @param username username of the user who is signing up
     * @param signUpChallengeResponse response to the signUp challenge posted
     * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation.
     */
    @WorkerThread
    public SignUpResult confirmSignUp(final String username,
                                      final String signUpChallengeResponse,
                                      final Map clientMetadata) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_confirmSignUp(username, signUpChallengeResponse,
                clientMetadata, internalCallback));
    }
    /**
     * Confirm the sign-up request with follow-up information
     *
     * @param username username/email address/handle of the user who is signing up
     * @param signUpChallengeResponse response to the signUp challenge posted
     * @param callback the callback will be invoked to notify the success or
     *                 failure of the confirmSignUp operation
     */
    @AnyThread
    public void confirmSignUp(final String username,
                              final String signUpChallengeResponse,
                              final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_confirmSignUp(username, signUpChallengeResponse,
                Collections.emptyMap(), internalCallback));
    }
    /**
     * Confirm the sign-up request with follow-up information
     *
     * @param username username of the user who is signing up
     * @param signUpChallengeResponse response to the signUp challenge posted
     */
    @WorkerThread
    public SignUpResult confirmSignUp(final String username,
                                      final String signUpChallengeResponse) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_confirmSignUp(username, signUpChallengeResponse,
                Collections.emptyMap(), internalCallback));
    }
    private Runnable _confirmSignUp(final String username,
                                    final String signUpChallengeResponse,
                                    final Map clientMetadata,
                                    final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                userpool.getUser(username).confirmSignUp(signUpChallengeResponse,
                        false, clientMetadata, new GenericHandler() {
                    @Override
                    public void onSuccess() {
                        callback.onResult(new SignUpResult(
                                true,
                                null,
                                null
                        ));
                        signUpUser = null;
                    }
                    @Override
                    public void onFailure(Exception exception) {
                        callback.onError(exception);
                    }
                });
            }
        };
    }
    /**
     * Used when a user has attempted sign-up previously and wants to continue the process.
     * Note: If the user tries through the normal process with the same username, then it will
     * fail and this method is required.
     *
     * @param username
     * @param callback
     */
    @AnyThread
    public void resendSignUp(
            final String username,
            final Callback callback) {
        resendSignUp(username, Collections.emptyMap(), callback);
    }
    /**
     * Used when a user has attempted sign-up previously and wants to continue the process.
     * Note: If the user tries through the normal process with the same username, then it will
     * fail and this method is required.
     *
     * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for
     *                       custom workflow.
     * @param username
     * @param callback
     */
    @AnyThread
    public void resendSignUp(
            final String username,
            final Map clientMetadata,
            final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_resendSignUp(username, clientMetadata, internalCallback));
    }
    /**
     * Used when a user has attempted sign-up previously and wants to continue the process.
     * Note: If the user tries through the normal process with the same username, then it will
     * fail and this method is required.
     *
     * @param username
     */
    @WorkerThread
    public SignUpResult resendSignUp(final String username) throws Exception {
        return resendSignUp(username, Collections.emptyMap());
    }
    /**
     * Used when a user has attempted sign-up previously and wants to continue the process.
     * Note: If the user tries through the normal process with the same username, then it will
     * fail and this method is required.
     *
     * @param clientMetadata A map of custom key-value pairs that is passed to the lambda function for
     *                       custom workflow.
     * @param username
     */
    @WorkerThread
    public SignUpResult resendSignUp(final String username, final Map clientMetadata) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_resendSignUp(username, clientMetadata, internalCallback));
    }
    private Runnable _resendSignUp(
            final String username,
            final Map clientMetadata,
            final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                userpool.getUser(username).resendConfirmationCodeInBackground(
                        clientMetadata,
                        new VerificationHandler() {
                            @Override
                            public void onSuccess(CognitoUserCodeDeliveryDetails verificationCodeDeliveryMedium) {
                                UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails(
                                        verificationCodeDeliveryMedium.getDestination(),
                                        verificationCodeDeliveryMedium.getDeliveryMedium(),
                                        verificationCodeDeliveryMedium.getAttributeName()
                                );
                                callback.onResult(new SignUpResult(
                                        false,
                                        userCodeDeliveryDetails,
                                        null
                                ));
                            }
                            @Override
                            public void onFailure(Exception exception) {
                                callback.onError(exception);
                            }
                        }
                );
            }
        };
    }
    /**
     * Used to reset password if user forgot the old password.
     *
     * @param username username of the user trying to reset password.
     * @param clientMetadata meta data to be passed to the lambdas invoked by confirm sign up operation.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 forgot password operation
     */
    @AnyThread
    public void forgotPassword(final String username,
                               final Map clientMetadata,
                               final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_forgotPassword(username, clientMetadata, internalCallback));
    }
    /**
     * Used to reset password if user forgot the old password.
     *
     * @param username username of the user trying to reset password.
     */
    @WorkerThread
    public ForgotPasswordResult forgotPassword(final String username,
                                               final Map clientMetadata) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_forgotPassword(username, clientMetadata, internalCallback));
    }
    /**
     * Used to reset password if user forgot the old password.
     *
     * @param username username of the user trying to reset password.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 forgot password operation
     */
    @AnyThread
    public void forgotPassword(final String username,
                               final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_forgotPassword(username, Collections.emptyMap(), internalCallback));
    }
    /**
     * Used to reset password if user forgot the old password.
     *
     * @param username username of the user trying to reset password.
     */
    @WorkerThread
    public ForgotPasswordResult forgotPassword(final String username) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_forgotPassword(username, Collections.emptyMap(), internalCallback));
    }
    private Runnable _forgotPassword(final String username,
                                     final Map clientMetadata,
                                     final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                forgotPasswordCallback = new InternalCallback(callback);
                userpool.getUser(username).forgotPasswordInBackground(clientMetadata, new ForgotPasswordHandler() {
                    @Override
                    public void onSuccess() {
                        forgotPasswordCallback
                                .onResult(new ForgotPasswordResult(ForgotPasswordState.DONE));
                    }
                    @Override
                    public void getResetCode(ForgotPasswordContinuation continuation) {
                        forgotPasswordContinuation = continuation;
                        ForgotPasswordResult result = new ForgotPasswordResult(ForgotPasswordState.CONFIRMATION_CODE);
                        CognitoUserCodeDeliveryDetails parameters = continuation.getParameters();
                        result.setParameters(new UserCodeDeliveryDetails(
                                parameters.getDestination(),
                                parameters.getDeliveryMedium(),
                                parameters.getAttributeName())
                        );
                        forgotPasswordCallback.onResult(result);
                    }
                    @Override
                    public void onFailure(Exception exception) {
                        forgotPasswordCallback.onError(exception);
                    }
                });
            }
        };
    }
    /**
     * Second method to call after {@link #forgotPassword(String)} to respond to any challenges
     * that the service may request.
     *
     * @param password new password
     * @param forgotPasswordChallengeResponse response to the forgot password challenge posted
     * @param clientMetadata metadata to be passed to the lambda invoked by this operation.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 confirm forgot password operation
     */
    @AnyThread
    public void confirmForgotPassword(final String password,
                                      final String forgotPasswordChallengeResponse,
                                      final Map clientMetadata,
                                      final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse, clientMetadata, internalCallback));
    }
    /**
     * Second method to call after {@link #forgotPassword(String)} to respond to any challenges
     * that the service may request.
     *
     * @param password new password.
     * @param clientMetadata metadata to be passed to the lambda invoked by this operation.
     * @param forgotPasswordChallengeResponse response to the forgot password challenge posted.
     */
    @WorkerThread
    public ForgotPasswordResult confirmForgotPassword(final String password,
                                                      final Map clientMetadata,
                                                      final String forgotPasswordChallengeResponse) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_confirmForgotPassword(password, forgotPasswordChallengeResponse, clientMetadata, internalCallback));
    }
    /**
     * Second method to call after {@link #forgotPassword(String)} to respond to any challenges
     * that the service may request.
     *
     * @param password new password.
     * @param forgotPasswordChallengeResponse response to the forgot password challenge posted.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 confirm forgot password operation
     */
    @AnyThread
    @Deprecated
    public void confirmForgotPassword(final String password,
                                      final String forgotPasswordChallengeResponse,
                                      final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse,
                Collections.emptyMap(), internalCallback));
    }
    /**
     * Second method to call after {@link #forgotPassword(String)} to respond to any challenges
     * that the service may request.
     *
     * @param password new password.
     * @param forgotPasswordChallengeResponse response to the forgot password challenge posted.
     * @param callback callback will be invoked to notify the success or failure of the
     *                 confirm forgot password operation
     */
    @AnyThread
    public void confirmForgotPassword(final String username,
                                      final String password,
                                      final String forgotPasswordChallengeResponse,
                                      final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback<>(callback);
        ForgotPasswordHandler forgotPasswordHandler = new ForgotPasswordHandler() {
            @Override
            public void onSuccess() {
                callback.onResult(
                    new ForgotPasswordResult(
                        ForgotPasswordState.DONE
                    )
                );
            }
            @Override
            public void getResetCode(ForgotPasswordContinuation continuation) {
                callback.onResult(
                    new ForgotPasswordResult(
                        ForgotPasswordState.CONFIRMATION_CODE
                    )
                );
            }
            @Override
            public void onFailure(Exception exception) {
                callback.onError(exception);
            }
        };
        this.forgotPasswordContinuation = new ForgotPasswordContinuation(userpool.getUser(username),
                                                                         null,
                                                                         true,
                                                                         forgotPasswordHandler);
        internalCallback.async(_confirmForgotPassword(password, forgotPasswordChallengeResponse,
                                                      Collections.emptyMap(), internalCallback));
    }
    /**
     * Second method to call after {@link #forgotPassword(String)} to respond to any challenges
     * that the service may request.
     *
     * @param password new password.
     * @param forgotPasswordChallengeResponse response to the forgot password challenge posted.
     */
    @WorkerThread
    public ForgotPasswordResult confirmForgotPassword(final String password,
                                                      final String forgotPasswordChallengeResponse) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        return internalCallback.await(_confirmForgotPassword(password, forgotPasswordChallengeResponse,
                Collections.emptyMap(), internalCallback));
    }
    private Runnable _confirmForgotPassword(final String password,
                                            final String forgotPasswordChallengeResponse,
                                            final Map clientMetadata,
                                            final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                if (forgotPasswordContinuation == null) {
                    callback.onError(new IllegalStateException("confirmForgotPassword called before initiating forgotPassword"));
                    return;
                }
                forgotPasswordContinuation.setPassword(password);
                forgotPasswordContinuation.setVerificationCode(forgotPasswordChallengeResponse);
                forgotPasswordContinuation.setClientMetadata(clientMetadata);
                forgotPasswordCallback = new InternalCallback(callback);
                forgotPasswordContinuation.continueTask();
            }
        };
    }
    @AnyThread
    public void changePassword(final String oldPassword,
                               final String newPassword,
                               final Callback callback) {
        final InternalCallback internalCallback = new InternalCallback(callback);
        internalCallback.async(_changePassword(oldPassword, newPassword, internalCallback));
    }
    @WorkerThread
    public void changePassword(final String oldPassword,
                               final String newPassword) throws Exception {
        final InternalCallback internalCallback = new InternalCallback();
        internalCallback.await(_changePassword(oldPassword, newPassword, internalCallback));
    }
    private Runnable _changePassword(final String oldPassword,
                                     final String newPassword,
                                     final Callback callback) {
        return new Runnable() {
            @Override
            public void run() {
                userpool.getCurrentUser().changePassword(
                        oldPassword,
                        newPassword,
                        new GenericHandler() {
                            @Override
                            public void onSuccess() {
                                callback.onResult(null);
                            }
                            @Override
                            public void onFailure(Exception exception) {
                                callback.onError(exception);
                            }
                        }
                );
            }
        };
    }
    @AnyThread
    public void getUserAttributes(final Callback