package com.amazonaws.mobile.user;
//
// Copyright 2017 Amazon.com, Inc. or its affiliates (Amazon). All Rights Reserved.
//
// Code generated by AWS Mobile Hub. Amazon gives unlimited permission to
// copy, distribute and modify it.
//
// Source code generated from template: aws-my-sample-app-android v0.15
//
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.SDKGlobalConfiguration;
import com.amazonaws.auth.AWSBasicCognitoIdentityProvider;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.mobile.AWSConfiguration;
import com.amazonaws.mobile.util.ThreadUtils;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* The identity manager keeps track of the current sign-in provider and is responsible
* for caching credentials.
*/
public class IdentityManager {
/**
* Allows the application to get asynchronous response with user's
* unique identifier.
*/
public interface IdentityHandler {
/**
* Handles the user's unique identifier.
* @param identityId Amazon Cognito Identity ID which uniquely identifies
* the user.
*/
public void handleIdentityID(final String identityId);
/**
* Handles any error that might have occurred while getting the user's
* unique identifier from Amazon Cognito.
* @param exception exception
*/
public void handleError(final Exception exception);
}
/** Holder for the credentials provider, allowing the underlying provider to be swapped when necessary. */
private class AWSCredentialsProviderHolder implements AWSCredentialsProvider {
private volatile CognitoCachingCredentialsProvider underlyingProvider;
@Override
public AWSCredentials getCredentials() {
return underlyingProvider.getCredentials();
}
@Override
public void refresh() {
underlyingProvider.refresh();
}
public CognitoCachingCredentialsProvider getUnderlyingProvider() {
return underlyingProvider;
}
public void setUnderlyingProvider(final CognitoCachingCredentialsProvider underlyingProvider) {
this.underlyingProvider = underlyingProvider;
}
}
/** Log tag. */
private static final String LOG_TAG = IdentityManager.class.getSimpleName();
/** Holder for the credentials provider, allowing the underlying provider to be swapped when necessary. */
private final AWSCredentialsProviderHolder credentialsProviderHolder;
/** Application context. */
private final Context appContext;
/* Cognito client configuration. */
private final ClientConfiguration clientConfiguration;
/** Executor service for obtaining credentials in a background thread. */
private final ExecutorService executorService = Executors.newFixedThreadPool(2);
/** Current provider being used to obtain a Cognito access token. */
private IdentityProvider currentIdentityProvider = null;
/** Results adapter for adapting results that came from logging in with a provider. */
private SignInResultsAdapter resultsAdapter;
/** Keep tract of the currently registered SignInStateChangeListiners. */
private final HashSet<SignInStateChangeListener> signInStateChangeListeners = new HashSet<>();
/**
* Custom Cognito Identity Provider to handle refreshing the individual provider's tokens.
*/
public class AWSRefreshingCognitoIdentityProvider extends AWSBasicCognitoIdentityProvider {
/** Log tag. */
private final String LOG_TAG = AWSRefreshingCognitoIdentityProvider.class.getSimpleName();
public AWSRefreshingCognitoIdentityProvider(final String accountId,
final String identityPoolId,
final ClientConfiguration clientConfiguration,
final Regions regions) {
super(accountId, identityPoolId, clientConfiguration);
// Force refreshing ID provider to use same region as caching credentials provider
this.cib.setRegion(Region.getRegion(regions));
}
@Override
public String refresh() {
Log.d(LOG_TAG, "Refreshing token...");
if (currentIdentityProvider != null) {
final String newToken = currentIdentityProvider.refreshToken();
getLogins().put(currentIdentityProvider.getCognitoLoginKey(), newToken);
}
return super.refresh();
}
}
/**
* Constructor. Initializes the cognito credentials provider.
* @param appContext the application context.
* @param clientConfiguration the client configuration options such as retries and timeouts.
*/
public IdentityManager(final Context appContext, final ClientConfiguration clientConfiguration) {
Log.d(LOG_TAG, "IdentityManager init");
this.appContext = appContext;
this.clientConfiguration = clientConfiguration;
credentialsProviderHolder = new AWSCredentialsProviderHolder();
initializeCognito(this.appContext, this.clientConfiguration);
// Ensures that userID is cached.
getUserID(null);
}
private void setCredentialsProvider(final Context context,
final CognitoCachingCredentialsProvider cachingCredentialsProvider) {
credentialsProviderHolder.setUnderlyingProvider(cachingCredentialsProvider);
}
private void initializeCognito(final Context context, final ClientConfiguration clientConfiguration) {
final AWSRefreshingCognitoIdentityProvider refreshingCredentialsProvider =
new AWSRefreshingCognitoIdentityProvider(null, AWSConfiguration.AMAZON_COGNITO_IDENTITY_POOL_ID,
clientConfiguration, AWSConfiguration.AMAZON_COGNITO_REGION);
setCredentialsProvider(context,
new CognitoCachingCredentialsProvider(context, refreshingCredentialsProvider,
AWSConfiguration.AMAZON_COGNITO_REGION, clientConfiguration));
}
/**
* @return true if the cached Cognito credentials are expired, otherwise false.
*/
public boolean areCredentialsExpired() {
final Date credentialsExpirationDate =
credentialsProviderHolder.getUnderlyingProvider().getSessionCredentitalsExpiration();
if (credentialsExpirationDate == null) {
Log.d(LOG_TAG, "Credentials are EXPIRED.");
return true;
}
long currentTime = System.currentTimeMillis() -
(long)(SDKGlobalConfiguration.getGlobalTimeOffset() * 1000);
final boolean credsAreExpired =
(credentialsExpirationDate.getTime() - currentTime) < 0;
Log.d(LOG_TAG, "Credentials are " + (credsAreExpired ? "EXPIRED." : "OK"));
return credsAreExpired;
}
/**
* @return the Cognito credentials provider.
*/
public AWSCredentialsProvider getCredentialsProvider() {
return this.credentialsProviderHolder;
}
public CognitoCachingCredentialsProvider getUnderlyingProvider() {
return this.credentialsProviderHolder.getUnderlyingProvider();
}
/**
* Gets the cached unique identifier for the user.
* @return the cached unique identifier for the user.
*/
public String getCachedUserID() {
return credentialsProviderHolder.getUnderlyingProvider().getCachedIdentityId();
}
/**
* Gets the user's unique identifier. This method can be called from
* any thread.
* @param handler handles the unique identifier for the user
*/
public void getUserID(final IdentityHandler handler) {
new Thread(new Runnable() {
Exception exception = null;
@Override
public void run() {
String identityId = null;
try {
// Retrieve the user identity on the background thread.
identityId = credentialsProviderHolder.getUnderlyingProvider().getIdentityId();
} catch (final Exception exception) {
this.exception = exception;
Log.e(LOG_TAG, exception.getMessage(), exception);
} finally {
if (handler == null) {
return;
}
final String result = identityId;
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
if (exception != null) {
handler.handleError(exception);
return;
}
handler.handleIdentityID(result);
}
});
}
}
}).start();
}
/**
* Implement this interface to get callbacks for the results to a sign-in operation.
*/
public interface SignInResultsHandler {
/**
* Sign-in was successful.
* @param provider sign-in identity provider
*/
void onSuccess(IdentityProvider provider);
/**
* Sign-in was cancelled by the user.
* @param provider sign-in identity provider
*/
void onCancel(IdentityProvider provider);
/**
* Sign-in failed.
* @param provider sign-in identity provider
* @param ex exception that occurred
*/
void onError(IdentityProvider provider, Exception ex);
}
/**
* Implement this interface to receive callbacks when the user's sign-in state changes
* from signed-in to not signed-in or vice versa.
*/
public interface SignInStateChangeListener {
/**
* Invoked when the user completes sign-in.
*/
void onUserSignedIn();
/**
* Invoked when the user signs out.
*/
void onUserSignedOut();
}
/**
* The adapter to handle results that come back from Cognito as well as handle the result from
* any login providers.
*/
private class SignInResultsAdapter implements SignInResultsHandler {
final private SignInResultsHandler handler;
public SignInResultsAdapter(final SignInResultsHandler handler) {
this.handler = handler;
}
public void onSuccess(final IdentityProvider provider) {
Log.d(LOG_TAG,
String.format("SignInResultsAdapter.onSuccess(): %s provider sign-in succeeded.",
provider.getDisplayName()));
// Update cognito login with the token.
loginWithProvider(provider);
}
private void onCognitoSuccess() {
Log.d(LOG_TAG, "SignInResultsAdapter.onCognitoSuccess()");
handler.onSuccess(currentIdentityProvider);
}
private void onCognitoError(final Exception ex) {
Log.d(LOG_TAG, "SignInResultsAdapter.onCognitoError()", ex);
final IdentityProvider provider = currentIdentityProvider;
// Sign out of parent provider. This clears the currentIdentityProvider.
IdentityManager.this.signOut();
handler.onError(provider, ex);
}
public void onCancel(final IdentityProvider provider) {
Log.d(LOG_TAG,
String.format("SignInResultsAdapter.onCancel(): %s provider sign-in canceled.",
provider.getDisplayName()));
handler.onCancel(provider);
}
public void onError(final IdentityProvider provider, final Exception ex) {
Log.e(LOG_TAG,
String.format("SignInResultsAdapter.onError(): %s provider error. %s",
provider.getDisplayName(), ex.getMessage()), ex);
handler.onError(provider, ex);
}
}
/**
* Add a listener to receive callbacks when sign-in or sign-out occur. The listener
* methods will always be called on a background thread.
* @param listener the sign-in state change listener.
*/
public void addSignInStateChangeListener(final SignInStateChangeListener listener) {
synchronized (signInStateChangeListeners) {
signInStateChangeListeners.add(listener);
}
}
/**
* Remove a listener from receiving callbacks when sign-in or sign-out occur.
* @param listener the sign-in state change listener.
*/
public void removeSignInStateChangeListener(final SignInStateChangeListener listener) {
synchronized (signInStateChangeListeners) {
signInStateChangeListeners.remove(listener);
}
}
/**
* Set the results handler that will be used for results when calling loginWithProvider.
* @param signInResultsHandler the results handler.
*/
public void setResultsHandler(final SignInResultsHandler signInResultsHandler) {
if (signInResultsHandler == null) {
throw new IllegalArgumentException("signInResultsHandler cannot be null.");
}
this.resultsAdapter = new SignInResultsAdapter(signInResultsHandler);
}
/**
* Call getResultsAdapter to get the IdentityManager's handler that adapts results before
* sending them back to the handler set by {@link #setResultsHandler(SignInResultsHandler)}
* @return the Identity Manager's results adapter.
*/
public SignInResultsAdapter getResultsAdapter() {
return resultsAdapter;
}
/**
* @return true if Cognito credentials have been obtained with at least one provider.
*/
public boolean isUserSignedIn() {
final Map<String, String> logins = credentialsProviderHolder.getUnderlyingProvider().getLogins();
if (logins == null || logins.size() == 0)
return false;
return true;
}
/**
* Sign out of the currently in use credentials provider and clear Cognito credentials.
*/
public void signOut() {
Log.d(LOG_TAG, "Signing out...");
if (currentIdentityProvider != null) {
currentIdentityProvider.signOut();
credentialsProviderHolder.getUnderlyingProvider().clear();
currentIdentityProvider = null;
// Ensure that un-auth userID is cached.
getUserID(null);
// Notify state change listeners of sign out.
synchronized (signInStateChangeListeners) {
for (final SignInStateChangeListener listener : signInStateChangeListeners) {
listener.onUserSignedOut();
}
}
}
}
private void refreshCredentialWithLogins(final Map<String, String> loginMap) {
final CognitoCachingCredentialsProvider credentialsProvider =
credentialsProviderHolder.getUnderlyingProvider();
credentialsProvider.clear();
credentialsProvider.withLogins(loginMap);
// Calling refresh is equivalent to calling getIdentityId() + getCredentials().
Log.d(getClass().getSimpleName(), "refresh credentials");
credentialsProvider.refresh();
Log.d(getClass().getSimpleName(), "Cognito ID: " + credentialsProvider.getIdentityId());
Log.d(getClass().getSimpleName(), "Cognito Credentials: " + credentialsProvider.getCredentials());
}
/**
* Login with an identity provider (ie. Facebook, Twitter, etc.).
* @param provider A sign-in provider.
*/
public void loginWithProvider(final IdentityProvider provider) {
Log.d(LOG_TAG, "loginWithProvider");
final Map<String, String> loginMap = new HashMap<String, String>();
loginMap.put(provider.getCognitoLoginKey(), provider.getToken());
currentIdentityProvider = provider;
initializeCognito(this.appContext, this.clientConfiguration);
executorService.submit(new Runnable() {
@Override
public void run() {
try {
refreshCredentialWithLogins(loginMap);
} catch (Exception ex) {
resultsAdapter.onCognitoError(ex);
return;
}
resultsAdapter.onCognitoSuccess();
// Notify state change listeners of sign out.
synchronized (signInStateChangeListeners) {
for (final SignInStateChangeListener listener : signInStateChangeListeners) {
listener.onUserSignedIn();
}
}
}
});
}
/**
* Gets the current provider.
* @return current provider or null if not signed-in
*/
public IdentityProvider getCurrentIdentityProvider() {
return currentIdentityProvider;
}
// local cache of the user image of currentIdentityProvider.getUserImageUrl();
private Bitmap userImage = null;
private void loadUserImage(final String userImageUrl) {
if (userImageUrl == null) {
userImage = null;
return;
}
try {
final InputStream is = new URL(userImageUrl).openStream();
userImage = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
Log.w(LOG_TAG, "Failed to prefetch user image: " + userImageUrl, e);
// clear user image
userImage = null;
}
}
/**
* Reload the user info and image in the background.
*
* @param provider sign-in provider
* @param onReloadComplete Runnable to be executed on the main thread after user info
* and user image is reloaded.
*/
public void loadUserInfoAndImage(final IdentityProvider provider, final Runnable onReloadComplete) {
executorService.submit(new Runnable() {
@Override
public void run() {
provider.reloadUserInfo();
// preload user image
loadUserImage(provider.getUserImageUrl());
ThreadUtils.runOnUiThread(onReloadComplete);
}
});
}
/**
* Convenient method to get the user image of the current identity provider.
* @return user image of the current identity provider, or null if not signed in or unavailable
*/
public Bitmap getUserImage() {
return userImage;
}
/**
* Convenient method to get the user name from the current identity provider.
* @return user name from the current identity provider, or null if not signed in
*/
public String getUserName() {
return currentIdentityProvider == null ? null : currentIdentityProvider.getUserName();
}
}