diff --git a/CHANGELOG.md b/CHANGELOG.md index c45f5450308..4454e083909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Change Log - AWS SDK for Android +## [Release 2.6.0](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.6.0) + +### New Features: + +- **AWS Auth SDK** + - Added new SDK for configurable User SignIn Screen with Amazon Cognito UserPools, Facebook SignIn and Google SignIn. + +- **AWS Core Runtime** + - Added support for a configuration file `awsconfiguration.json` that can be used to construct: + - `CognitoCredentialsProvider`, `CognitoCachingCredentialsProvider`, `CognitoUserPool`, `TransferUtility`, `DynamoDBMapper`, `PinpointConfiguration`, `CognitoSyncManager`, and `LambdaInvokerFactory`. + +### Improvements: + +- **AWS S3** + - Add builder pattern constructor to `TransferUtility`. + - Add default bucket property in `TransferUtility` builder. The default bucket will be used when no bucket is specified. + +- **AWS Lambda** + - Add builder pattern constructor to `LambdaInvokerFactory`. + +- **Amazon DynamoDB** + - Add builder pattern constructor to `DynamoDBMapper`. + +- **Amazon Pinpoint** + - Add configuration option to post notifications even if the app is in the foreground. + +### Bug Fixes: + +- **Amazon Pinpoint** + - Fixed bug that caused Pinpoint endpoint profile to incorrectly calculate the number of profile attributes and metrics. + ## [Release 2.4.7](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.4.7) ### Improvements: diff --git a/aws-android-sdk-apigateway-core/pom.xml b/aws-android-sdk-apigateway-core/pom.xml index 50a68ff70b0..6331ad326e2 100644 --- a/aws-android-sdk-apigateway-core/pom.xml +++ b/aws-android-sdk-apigateway-core/pom.xml @@ -12,7 +12,7 @@ com.amazonaws aws-android-sdk-pom - 2.4.7 + 2.6.0 @@ -20,7 +20,7 @@ com.amazonaws aws-android-sdk-core false - 2.4.7 + 2.6.0 diff --git a/aws-android-sdk-auth-core/pom.xml b/aws-android-sdk-auth-core/pom.xml new file mode 100644 index 00000000000..0a88ad4ef2b --- /dev/null +++ b/aws-android-sdk-auth-core/pom.xml @@ -0,0 +1,84 @@ + + 4.0.0 + com.amazonaws + aws-android-sdk-auth-core + aar + AWS SDK for Android - AWS Authentication Core + The AWS Android SDK for AWS Authentication Core module holds the client classes that are used for enabling communication with Amazon CognitoIdentityProvider, Amazon Cognito UserPools, Facebook and Google SignIn Providers + http://aws.amazon.com/sdkforandroid + + + + UTF-8 + + + UTF-8 + + + + + com.amazonaws + aws-android-sdk-pom + 2.6.0 + + + + + android-support + file://${env.ANDROID_HOME}/extras/android/m2repository/ + + + + + + com.amazonaws + aws-android-sdk-core + false + 2.6.0 + + + + com.google.android + android + 4.1.1.4 + provided + + + + com.android.support + support-v4 + 24.2.0 + aar + provided + + + + + + + com.simpligility.maven.plugins + android-maven-plugin + 4.5.0 + true + + + 11 + 19.1.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/aws-android-sdk-auth-core/src/main/AndroidManifest.xml b/aws-android-sdk-auth-core/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..d0d3b575f78 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/DefaultSignInResultHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/DefaultSignInResultHandler.java new file mode 100644 index 00000000000..eb4a3166c6c --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/DefaultSignInResultHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.util.Log; +import android.widget.Toast; + +import com.amazonaws.mobile.auth.core.R; + +/** + * A default base class easing the work required for implementing the SignInResultHandler for + * {@link IdentityManager#signInOrSignUp(Context, SignInResultHandler)} by providing default + * behavior in the case that the user cancels signing in or encounters an error. The default for + * canceling is to toast that sign-in was canceled. The default for a sign-in error is to show + * an alert dialog specifying the error message. + */ +public abstract class DefaultSignInResultHandler implements SignInResultHandler { + private static final String LOG_TAG = DefaultSignInResultHandler.class.getSimpleName(); + + /** + * User cancelled signing in with a provider on the sign-in activity. + * Note: The user is still on the sign-in activity when this call is made. + * @param provider the provider the user canceled with. + */ + public void onIntermediateProviderCancel(Activity callingActivity, IdentityProvider provider) { + Log.d(LOG_TAG, String.format("%s Sign-In flow is canceled", provider.getDisplayName())); + } + + /** + * User encountered an error when attempting to sign-in with a provider. + * Note: The user is still on the sign-in activity when this call is made. + * @param provider the provider the user attempted to sign-in with that encountered an error. + * @param ex the exception that occurred. + */ + public void onIntermediateProviderError(Activity callingActivity, IdentityProvider provider, Exception ex) { + final String failureFormatString = callingActivity.getString(R.string.sign_in_failure_message_format); + Log.e(LOG_TAG, String.format(failureFormatString, + provider.getDisplayName(), ex.getMessage()), ex); + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityHandler.java new file mode 100644 index 00000000000..d634728f55d --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +/** + * Allows the application to get an 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. + */ + void onIdentityId(final String identityId); + + /** + * Handles any error that might have occurred while getting the user's + * unique identifier from Amazon Cognito. + * @param exception exception + */ + void handleError(final Exception exception); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityManager.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityManager.java new file mode 100644 index 00000000000..eca435f3443 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityManager.java @@ -0,0 +1,832 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.amazonaws.ClientConfiguration; +import com.amazonaws.SDKGlobalConfiguration; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.auth.AWSBasicCognitoIdentityProvider; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.CognitoCachingCredentialsProvider; + +import com.amazonaws.mobile.auth.core.signin.AuthException; +import com.amazonaws.mobile.auth.core.signin.CognitoAuthException; +import com.amazonaws.mobile.auth.core.signin.ProviderAuthException; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.SignInProvider; +import com.amazonaws.mobile.auth.core.signin.SignInProviderResultHandler; +import com.amazonaws.mobile.auth.core.internal.util.ThreadUtils; + +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; + +import java.io.IOException; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +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 { + + /** Holder for the credentials provider, allowing the underlying provider to be swapped when necessary. */ + private class AWSCredentialsProviderHolder implements AWSCredentialsProvider { + + /** Reference to the credentials provider. */ + private volatile CognitoCachingCredentialsProvider underlyingProvider; + + @Override + public AWSCredentials getCredentials() { + return underlyingProvider.getCredentials(); + } + + @Override + public void refresh() { + underlyingProvider.refresh(); + } + + private CognitoCachingCredentialsProvider getUnderlyingProvider() { + return underlyingProvider; + } + + private void setUnderlyingProvider(final CognitoCachingCredentialsProvider underlyingProvider) { + // if the current underlyingProvider is not null + this.underlyingProvider = underlyingProvider; + } + } + + /** Log tag. */ + private static final String LOG_TAG = IdentityManager.class.getSimpleName(); + + /** AWS Configuration json file */ + private static final String AWS_CONFIGURATION_FILE = "awsconfiguration.json"; + + /** Holder for the credentials provider, allowing the underlying provider to be swapped when necessary. */ + private final AWSCredentialsProviderHolder credentialsProviderHolder; + + /** Application context. */ + private final Context appContext; + + /** Configuration for the mobile helper. */ + private AWSConfiguration awsConfiguration; + + /* Cognito client configuration. */ + private final ClientConfiguration clientConfiguration; + + /** Executor service for obtaining credentials in a background thread. */ + private final ExecutorService executorService = Executors.newFixedThreadPool(4); + + /** Timeout CountdownLatch for doStartupAuth(). */ + private final CountDownLatch startupAuthTimeoutLatch = new CountDownLatch(1); + + /** Keep track of the registered sign-in providers. */ + private final List> signInProviderClasses + = new LinkedList>(); + + /** Current provider beingIdentityProviderType used to obtain a Cognito access token. */ + private volatile IdentityProvider currentIdentityProvider = null; + + /** Results adapter for adapting results that came from logging in with a provider. */ + private SignInProviderResultAdapter resultsAdapter; + + /** Keep track of the currently registered SignInStateChangeListeners. */ + private final HashSet signInStateChangeListeners + = new HashSet(); + + /** Reference to the default identity manager */ + private static IdentityManager defaultIdentityManager = null; + + /** + * Shared Preferences Name + * This key is used to store credentials in SharedPreferences + * and used by CognitoCachingCredentialsProvider + */ + private static final String SHARED_PREF_NAME = "com.amazonaws.android.auth"; + + /** Shared Preferences Key */ + private static final String EXPIRATION_KEY = "expirationDate"; + + /** + * Custom Cognito Identity Provider to handle refreshing the individual provider's tokens. + */ + private 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 with the application context and the AWSConfiguration passed in. + * Creates a default ClientConfiguration with the user agent from AWSConfiguration. + * + * @param context the application context. + * @param awsConfiguration the aws configuration. + */ + public IdentityManager(final Context context, + final AWSConfiguration awsConfiguration) { + this.appContext = context.getApplicationContext(); + this.awsConfiguration = awsConfiguration; + this.clientConfiguration = new ClientConfiguration().withUserAgent(awsConfiguration.getUserAgent()); + this.credentialsProviderHolder = new AWSCredentialsProviderHolder(); + initializeCognito(this.appContext, this.clientConfiguration); + } + + /** + * Constructor. + * Initializes with the application context, the AWSConfiguration + * and the ClientConfiguration passed in. + * Read the UserAgent from AWSConfiguration and set in ClientConfiguration. + * + * @param context the application context. + * @param awsConfiguration the aws configuration. + * @param clientConfiguration the client configuration options such as retries and timeouts. + */ + public IdentityManager(final Context context, + final AWSConfiguration awsConfiguration, + final ClientConfiguration clientConfiguration) { + this.appContext = context.getApplicationContext(); + this.awsConfiguration = awsConfiguration; + this.clientConfiguration = clientConfiguration; + + final String userAgent = this.awsConfiguration.getUserAgent(); + String currentUserAgent = this.clientConfiguration.getUserAgent(); + currentUserAgent = currentUserAgent != null ? currentUserAgent : ""; + + if (userAgent != null && userAgent != currentUserAgent) { + this.clientConfiguration.setUserAgent(currentUserAgent.trim() + " " + userAgent); + } + + this.credentialsProviderHolder = new AWSCredentialsProviderHolder(); + initializeCognito(this.appContext, this.clientConfiguration); + } + + /** + * Constructor. + * Initializes with the activity context, application's credentials provider + * that provides the identity and the client configuration. + * + * @param context the application context. + * @param awsConfiguration the aws configuration. + * @param clientConfiguration the client configuration options such as retries and timeouts. + */ + public IdentityManager(final Context context, + final CognitoCachingCredentialsProvider credentialsProvider, + final ClientConfiguration clientConfiguration) { + this.appContext = context.getApplicationContext(); + this.clientConfiguration = clientConfiguration; + this.credentialsProviderHolder = new AWSCredentialsProviderHolder(); + setCredentialsProvider(context, credentialsProvider); + } + + /** + * Return the default IdentityManager + * + * @return defaultIdentityManager The default IdentityManager object + */ + public static IdentityManager getDefaultIdentityManager() { + return defaultIdentityManager; + } + + /** + * Set the IdentityManager object created as the default + * + * @param identityManager The IdentityManager object to be set as the default + */ + public static void setDefaultIdentityManager(IdentityManager identityManager) { + defaultIdentityManager = null; + defaultIdentityManager = identityManager; + } + + /** + * Retrieve the AWSConfiguration object + * + * @return AWSConfiguration Return the reference to the AWSConfiguration object + */ + public AWSConfiguration getConfiguration() { + return this.awsConfiguration; + } + + /** + * Check if the credentials are expired. + * + * @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; + } + + /** + * Retrieve the reference to AWSCredentialsProvider object. + * + * @return the Cognito credentials provider. + */ + public AWSCredentialsProvider getCredentialsProvider() { + return this.credentialsProviderHolder; + } + + /** + * Retrieve the reference to CognitoCachingCredentialsProvider object. + * + * @return the Cognito Caching Credentials Provider + */ + 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) { + + executorService.submit(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 { + final String result = identityId; + Log.d(LOG_TAG, "Got user ID: " + identityId); + + // Lint doesn't like early return inside a finally block, so nesting further inside the if here. + if (handler != null) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + if (exception != null) { + handler.handleError(exception); + return; + } + + handler.onIdentityId(result); + } + }); + } + } + } + }); + } + + /** + * The adapter to handle results that come back from Cognito as well as handle the result from + * any login providers. + */ + private class SignInProviderResultAdapter implements SignInProviderResultHandler { + final private SignInProviderResultHandler handler; + + private SignInProviderResultAdapter(final SignInProviderResultHandler handler) { + this.handler = handler; + } + + @Override + public void onSuccess(final IdentityProvider provider) { + Log.d(LOG_TAG, + String.format("SignInProviderResultAdapter.onSuccess(): %s provider sign-in succeeded.", + provider.getDisplayName())); + // Update Cognito login with the token. + federateWithProvider(provider); + } + + private void onCognitoSuccess() { + Log.d(LOG_TAG, "SignInProviderResultAdapter.onCognitoSuccess()"); + handler.onSuccess(currentIdentityProvider); + } + + private void onCognitoError(final Exception ex) { + Log.d(LOG_TAG, "SignInProviderResultAdapter.onCognitoError()", ex); + final IdentityProvider provider = currentIdentityProvider; + // Sign out of parent provider. This clears the currentIdentityProvider. + IdentityManager.this.signOut(); + handler.onError(provider, new CognitoAuthException(provider, ex)); + } + + @Override + public void onCancel(final IdentityProvider provider) { + Log.d(LOG_TAG, String.format( + "SignInProviderResultAdapter.onCancel(): %s provider sign-in canceled.", + provider.getDisplayName())); + handler.onCancel(provider); + } + + @Override + public void onError(final IdentityProvider provider, final Exception ex) { + Log.e(LOG_TAG, + String.format("SignInProviderResultAdapter.onError(): %s provider error. %s", + provider.getDisplayName(), ex.getMessage()), ex); + handler.onError(provider, new ProviderAuthException(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); + } + } + + /** + * Call getResultsAdapter to get the IdentityManager's handler that adapts results before + * sending them back to the handler set by {@link #setProviderResultsHandler(SignInProviderResultHandler)} + * + * @return the Identity Manager's results adapter. + */ + public SignInProviderResultAdapter getResultsAdapter() { + return resultsAdapter; + } + + /** + * Sign out of the current identity provider, and clear Cognito credentials. + * Note: This call does not attempt to obtain un-auth credentials. To obtain an unauthenticated + * anonymous (guest) identity, call {@link #getUserID(IdentityHandler)}. + */ + public void signOut() { + Log.d(LOG_TAG, "Signing out..."); + + if (currentIdentityProvider != null) { + executorService.submit(new Runnable() { + @Override + public void run() { + currentIdentityProvider.signOut(); + credentialsProviderHolder.getUnderlyingProvider().clear(); + currentIdentityProvider = null; + + // Notify state change listeners of sign out. + synchronized (signInStateChangeListeners) { + for (final SignInStateChangeListener listener : signInStateChangeListeners) { + listener.onUserSignedOut(); + } + } + } + }); + } + } + + private void refreshCredentialWithLogins(final Map loginMap) { + + final CognitoCachingCredentialsProvider credentialsProvider = + credentialsProviderHolder.getUnderlyingProvider(); + credentialsProvider.clear(); + credentialsProvider.withLogins(loginMap); + + // Calling refresh is equivalent to calling getIdentityId() + getCredentials(). + Log.d(LOG_TAG, "refresh credentials"); + credentialsProvider.refresh(); + + // expire credentials in 2 minutes. + appContext.getSharedPreferences(SHARED_PREF_NAME, + Context.MODE_PRIVATE).edit() + .putLong(credentialsProvider.getIdentityPoolId() + "." + EXPIRATION_KEY, System.currentTimeMillis() + (510*1000)) + .commit(); + + } + + /** + * Set the results handler that will be used for results when calling federateWithProvider. + * + * @param signInProviderResultHandler the results handler. + */ + public void setProviderResultsHandler(final SignInProviderResultHandler signInProviderResultHandler) { + if (signInProviderResultHandler == null) { + throw new IllegalArgumentException("signInProviderResultHandler cannot be null."); + } + this.resultsAdapter = new SignInProviderResultAdapter(signInProviderResultHandler); + } + + /** + * Login with an identity provider (ie. Facebook, Twitter, etc.). + * + * @param provider A sign-in provider. + */ + public void federateWithProvider(final IdentityProvider provider) { + Log.d(LOG_TAG, "federateWithProvider"); + final Map loginMap = new HashMap(); + 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; + } + + /** + * Add a supported identity provider to your app. + * The provider will be presented as option to sign in to your app. + * + * @param providerClass the provider class for the identity provider. + */ + public void addSignInProvider(final Class providerClass) { + signInProviderClasses.add(providerClass); + } + + /** + * Gets the list of SignInProvider classes + * + * @return list of the signInProvider classes + */ + public Collection> getSignInProviderClasses() { + return signInProviderClasses; + } + + /** + * Check if user is signed in. + * + * @return true if Cognito credentials have been obtained with at least one provider. + */ + public boolean isUserSignedIn() { + final Map logins = credentialsProviderHolder.getUnderlyingProvider().getLogins(); + if (logins == null || logins.size() == 0) + return false; + return true; + } + + private void handleStartupAuthResult(final Activity callingActivity, + final StartupAuthResultHandler startupAuthResultHandler, + final AuthException authException, + final Exception unAuthException) { + runAfterStartupAuthDelay(callingActivity, new Runnable() { + @Override + public void run() { + startupAuthResultHandler.onComplete(new StartupAuthResult(IdentityManager.this, + new StartupAuthErrorDetails(authException, unAuthException))); + } + }); + } + + private void handleUnauthenticated(final Activity callingActivity, + final StartupAuthResultHandler startupAuthResultHandler, + final AuthException ex) { + // Optional Sign-in can dispose the sign-in manager right away here, while mandatory sign-in, needs + // it to stay around a bit longer, since it will be required to call sign-in or sign-up. + + SignInManager.dispose(); + + if (getCachedUserID() != null) { + handleStartupAuthResult(callingActivity, startupAuthResultHandler, ex, null); + } + + getUserID(new IdentityHandler() { + @Override + public void onIdentityId(final String identityId) { + handleStartupAuthResult(callingActivity, startupAuthResultHandler, ex, null); + } + + @Override + public void handleError(final Exception exception) { + handleStartupAuthResult(callingActivity, startupAuthResultHandler, ex, exception); + } + }); + handleStartupAuthResult(callingActivity, startupAuthResultHandler, ex, null); + } + + /** + * Starts an activity after the splash timeout. + * + * @param runnable runnable to run after the splash timeout expires. + */ + private void runAfterStartupAuthDelay(final Activity callingActivity, final Runnable runnable) { + executorService.submit(new Runnable() { + public void run() { + // Wait for the splash timeout expiry or for the user to tap. + try { + startupAuthTimeoutLatch.await(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, "Interrupted while waiting for startup auth minimum delay."); + } + + callingActivity.runOnUiThread(runnable); + } + }); + } + + /** + * This should be called from your app's splash activity upon start-up. If the user was previously + * signed in, this will attempt to refresh their identity using the previously sign-ed in provider. + * If the user was not previously signed in or their identity could not be refreshed with the + * previously signed in provider and sign-in is optional, it will attempt to obtain an unauthenticated (guest) + * identity. + * + * @param callingActivity the calling activity. + * @param startupAuthResultHandler a handler for returning results. + * @param minimumDelay the minimum delay to wait before returning the sign-in result. + */ + public void doStartupAuth(final Activity callingActivity, + final StartupAuthResultHandler startupAuthResultHandler, + final long minimumDelay) { + + executorService.submit(new Runnable() { + public void run() { + Log.d(LOG_TAG, "Starting up authentication..."); + final SignInManager signInManager = SignInManager.getInstance( + callingActivity.getApplicationContext()); + + if (signInManager == null) { + throw new IllegalStateException("You cannot pass null for identityManager."); + } + + final SignInProvider provider = signInManager.getPreviouslySignedInProvider(); + + // if the user was already previously signed-in to a provider. + if (provider != null) { + Log.d(LOG_TAG, "Refreshing credentials with identity provider " + provider.getDisplayName()); + // asynchronously handle refreshing credentials and call our handler. + signInManager.refreshCredentialsWithProvider(callingActivity, + provider, new SignInProviderResultHandler() { + + @Override + public void onSuccess(final IdentityProvider provider) { + // The sign-in manager is no longer needed once signed in. + SignInManager.dispose(); + + Log.d(LOG_TAG, "Successfully got credentials from identity provider '" + + provider.getDisplayName()); + + runAfterStartupAuthDelay(callingActivity, new Runnable() { + @Override + public void run() { + startupAuthResultHandler.onComplete(new StartupAuthResult(IdentityManager.this, null)); + } + }); + } + + @Override + public void onCancel(final IdentityProvider provider) { + // Should never happen. + Log.wtf(LOG_TAG, "Cancel can't happen when handling a previously signed-in user."); + } + + @Override + public void onError(final IdentityProvider provider, final Exception ex) { + Log.e(LOG_TAG, + String.format("Cognito credentials refresh with %s provider failed. Error: %s", + provider.getDisplayName(), ex.getMessage()), ex); + + if (ex instanceof AuthException) { + handleUnauthenticated(callingActivity, startupAuthResultHandler, + (AuthException) ex); + } else { + handleUnauthenticated(callingActivity, startupAuthResultHandler, + new AuthException(provider, ex)); + } + } + }); + } else { + handleUnauthenticated(callingActivity, startupAuthResultHandler, null); + } + + if (minimumDelay > 0) { + // Wait for the splash timeout. + try { + Thread.sleep(minimumDelay); + } catch (final InterruptedException ex) { + Log.i(LOG_TAG, "Interrupted while waiting for startup auth timeout."); + } + } + + // Expire the splash page delay. + startupAuthTimeoutLatch.countDown(); + } + }); + } + + /** + * This should be called from your app's splash activity upon start-up. If the user was previously + * signed in, this will attempt to refresh their identity using the previously sign-ed in provider. + * If the user was not previously signed in or their identity could not be refreshed with the + * previously signed in provider and sign-in is optional, it will attempt to obtain an unauthenticated (guest) + * identity. + * + * @param callingActivity the calling activity. + * @param startupAuthResultHandler a handler for returning results. + */ + public void doStartupAuth(final Activity callingActivity, + final StartupAuthResultHandler startupAuthResultHandler) { + doStartupAuth(callingActivity, startupAuthResultHandler, 0); + } + + /** + * Call this to ignore waiting for the remaining timeout delay. + */ + public void expireSignInTimeout() { + startupAuthTimeoutLatch.countDown(); + } + + /** + * Call setUpToAuthenticate to initiate sign-in with a provider. + * + * Note: This should not be called when already signed in with a provider. + * + * @param context context. + * @param signInResultHandler the results handler. + */ + public void setUpToAuthenticate(final Context context, + final SignInResultHandler signInResultHandler) { + // Start the sign-in activity. We do not finish the calling activity allowing the user to navigate back. + final SignInManager signInManager = SignInManager.getInstance( + context.getApplicationContext()); + signInManager.setResultHandler(signInResultHandler); + } + + private void setCredentialsProvider(final Context context, + final CognitoCachingCredentialsProvider cachingCredentialsProvider) { + credentialsProviderHolder.setUnderlyingProvider(cachingCredentialsProvider); + } + + /** + * The CognitoCachingCredentialProvider loads cached credentials when it is + * instantiated, however, it does not reload the login map, which must be reloaded + * in order to refresh the credentials. Therefore, currently cached credentials are + * only useful for unauthenticated users. + */ + private void initializeCognito(final Context context, + final ClientConfiguration clientConfiguration) { + + Log.d(LOG_TAG, "Initializing Cognito"); + + final String region = getCognitoIdentityRegion(); + Regions cognitoIdentityRegion = Regions.fromName(region); + + setCredentialsProvider(context, + new CognitoCachingCredentialsProvider(context, getCognitoIdentityPoolId(), + cognitoIdentityRegion, clientConfiguration)); + + final AWSRefreshingCognitoIdentityProvider refreshingCredentialsProvider = + new AWSRefreshingCognitoIdentityProvider(null, getCognitoIdentityPoolId(), + clientConfiguration, cognitoIdentityRegion); + + setCredentialsProvider(context, + new CognitoCachingCredentialsProvider(context, refreshingCredentialsProvider, + cognitoIdentityRegion, clientConfiguration)); + } + + /** + * Retrieve the Cognito IdentityPooldId from CognitoIdentity -> PoolId key + * + * @return PoolId + * @throws IllegalArgumentException + */ + private String getCognitoIdentityPoolId() throws IllegalArgumentException { + try { + return this.awsConfiguration + .optJsonObject("CredentialsProvider") + .getJSONObject("CognitoIdentity") + .getJSONObject(this.awsConfiguration.getConfiguration()) + .getString("PoolId"); + } catch (Exception exception) { + throw new IllegalArgumentException("Cannot access Cognito IdentityPoolId from the " + + AWS_CONFIGURATION_FILE + " file.", exception); + } + } + + /** + * Retrieve the Cognito Region from CognitoIdentity -> Region key + * + * @return CognitoIdentity Region + * @throws IllegalArgumentException + */ + private String getCognitoIdentityRegion() throws IllegalArgumentException { + try { + return this.awsConfiguration + .optJsonObject("CredentialsProvider") + .getJSONObject("CognitoIdentity") + .getJSONObject(this.awsConfiguration.getConfiguration()) + .getString("Region"); + } catch (Exception exception) { + throw new IllegalArgumentException("Cannot find the Cognito Region from the " + + AWS_CONFIGURATION_FILE + " file.", exception); + } + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityProvider.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityProvider.java new file mode 100644 index 00000000000..5602a8c1d3f --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/IdentityProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +import android.content.Context; + +import com.amazonaws.mobile.config.AWSConfiguration; + +/** + * Interface sign-in provider's supported by the IdentityManager must implement. + */ +public interface IdentityProvider { + + /** + * Method called upon constructing an identity provider for it to handle its initialization. + * + * @param context the context. + * @param configuration the configuration. + */ + void initialize(Context context, AWSConfiguration configuration); + + /** + * @return the display name for this provider. + */ + String getDisplayName(); + + /** + * @return the key used by Cognito in its login map when refreshing credentials. + */ + String getCognitoLoginKey(); + + /** + * Refreshes the state of whether the user is signed-in and returns the updated state. + * Note: This call may block, so it must not be called from the main thread. + * @return true if signed in with this provider, otherwise false. + */ + boolean refreshUserSignInState(); + + /** + * Call getToken to retrieve the access token from successful sign-in with this provider. + * Note: This call may block if the access token is not already cached. + * @return the access token suitable for use with Cognito. + */ + String getToken(); + + /** + * Refreshes the token if it has expired. + * Note: this call may block due to network access, and must be called from a background thread. + * @return the refreshed access token, or null if the token cannot be refreshed. + */ + String refreshToken(); + + /** + * Call signOut to sign out of this provider. + */ + void signOut(); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInResultHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInResultHandler.java new file mode 100644 index 00000000000..c2f67b704d2 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInResultHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +import android.app.Activity; + +/** + * Interface for handling results from calling IdentityManager's signInOrSignUp(). + */ +public interface SignInResultHandler { + /** + * Called when the user has obtained an identity by signing in with a provider. + * + * @param callingActivity the calling activity that should be finished. + * @param provider the provider or null if succeeded with an unauthenticated identity. + */ + void onSuccess(Activity callingActivity, IdentityProvider provider); + + /** + * User cancelled signing in with a provider on the sign-in activity. + * Note: The user is still on the sign-in activity when this call is made. + * @param provider the provider the user canceled with. + */ + void onIntermediateProviderCancel(Activity callingActivity, IdentityProvider provider); + + /** + * User encountered an error when attempting to sign-in with a provider. + * Note: The user is still on the sign-in activity when this call is made. + * @param provider the provider the user attempted to sign-in with that encountered an error. + * @param ex the exception that occurred. + */ + void onIntermediateProviderError(Activity callingActivity, IdentityProvider provider, Exception ex); + + /** + * User pressed back from the sign-in Activity. + * + * @return true if the activity should be finished, otherwise false. + */ + boolean onCancel(Activity callingActivity); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInStateChangeListener.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInStateChangeListener.java new file mode 100644 index 00000000000..1d1012ebd8b --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/SignInStateChangeListener.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +/** + * 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(); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthErrorDetails.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthErrorDetails.java new file mode 100644 index 00000000000..b4f0a88c688 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthErrorDetails.java @@ -0,0 +1,62 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +import com.amazonaws.mobile.auth.core.signin.AuthException; + +/** + * Encapsulates errors that may have happened during doStartupAuth(). + */ +public class StartupAuthErrorDetails { + private final AuthException authException; + private final Exception unauthException; + + public StartupAuthErrorDetails(final AuthException authException, + final Exception unauthException) { + this.authException = authException; + this.unauthException = unauthException; + } + + /** + * @return true if an error occurred refreshing a previously signed in provider, otherwise false. + */ + public boolean didErrorOccurRefreshingProvider() { + return authException != null; + } + + /** + * @return the AuthException that occurred while refreshing a provider, otherwise null. + */ + public AuthException getProviderRefreshException() { + return authException; + } + + /** + * @return true if an error occurred obtaining an unauthenticated identity, otherwise false. + */ + public boolean didErrorOccurObtainingUnauthenticatedIdentity() { + return unauthException != null; + } + + /** + * @return the exception that occurred while trying to obtain an unauthenticated (guest) identity. + */ + public Exception getUnauthenticatedErrorException() { + return unauthException; + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResult.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResult.java new file mode 100644 index 00000000000..35d47c28fa9 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResult.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +/** + * The result for IdentityManager's doStartupAuth(). + */ +public class StartupAuthResult { + private final IdentityManager identityManager; + private final StartupAuthErrorDetails errors; + + public StartupAuthResult(final IdentityManager identityManager, + final StartupAuthErrorDetails startupAuthErrorDetails) { + this.identityManager = identityManager; + this.errors = startupAuthErrorDetails; + } + + /** + * return true if signed in with an identity provider, otherwise false if signed in as an + * unauthenticated (guest) identity or not signed in at all. + */ + public boolean isUserSignedIn() { + return identityManager.isUserSignedIn(); + } + /** + * @return true if an unauthenticated (guest) identity was obtained, otherwise false. + */ + public boolean isUserAnonymous() { + return didObtainIdentity() && !isUserSignedIn(); + } + + /** + * @return true if an identity was obtained, either unauthenticated (guest) or authenticated with a provider. + */ + private boolean didObtainIdentity() { + return identityManager.getCachedUserID() != null; + } + + /** + * @return the identity manager. + */ + public IdentityManager getIdentityManager() { + return identityManager; + } + + /** + * @return StartupAuthErrorDetails object if errors occurred during the StartupAuthResult flow, otherwise null. + */ + public StartupAuthErrorDetails getErrorDetails() { + return errors; + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResultHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResultHandler.java new file mode 100644 index 00000000000..c16eba7a5b1 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/StartupAuthResultHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; + +/** + * Interface for calling IdentityManager's doStartupAuth(). + */ +public interface StartupAuthResultHandler { + /** + * Called when the startup auth flow is complete. + * For Optional Sign-in one of the following occurred: + * 1. No identity was obtained. + * 2. An unauthenticated (guest) identity was obtained. + * 3. An authenticated identity was obtained (using an identity provider). + * + * @param authResults the StartupAuthResult object containing the results for doStartupAuth(). + */ + void onComplete(StartupAuthResult authResults); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ThreadUtils.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ThreadUtils.java new file mode 100644 index 00000000000..a044ff5bcda --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ThreadUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.internal.util; + +import android.os.Handler; +import android.os.Looper; + +public class ThreadUtils { + + private ThreadUtils() { + + } + + /** + * Run a runnable on the Main (UI) Thread. + * @param runnable the runnable + */ + public static void runOnUiThread(final Runnable runnable) { + if (Looper.myLooper() != Looper.getMainLooper()) { + new Handler(Looper.getMainLooper()).post(runnable); + } else { + runnable.run(); + } + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ViewHelper.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ViewHelper.java new file mode 100644 index 00000000000..2b3064e8c74 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/ViewHelper.java @@ -0,0 +1,78 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.internal.util; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.widget.EditText; + +/** + * Utilities for Views. + * + */ +public final class ViewHelper { + /** + * Displays a modal dialog with an OK button. + * + * @param activity invoking activity + * @param title title to display for the dialog + * @param body content of the dialog + */ + public static void showDialog(final Activity activity, final String title, final String body) { + if (null == activity) { + return; + } + + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(title); + builder.setMessage(body); + builder.setNeutralButton(android.R.string.ok, null); + builder.show(); + } + + /** + * Displays a modal dialog. + * + * @param activity invoking activity + * @param title title to display for the dialog + * @param body content of the dialog + * @param positiveButton String for positive button + * @param negativeButton String for negative button + * @param negativeButtonListener the listener which should be invoked when a negative button is pressed + * @param positiveButtonListener the listener which should be invoked when a positive button is pressed + */ + public static void showDialog(final Activity activity, + final String title, + final String body, + final String positiveButton, + final DialogInterface.OnClickListener positiveButtonListener, + final String negativeButton, + final DialogInterface.OnClickListener negativeButtonListener) { + if (null == activity) { + return; + } + + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(title); + builder.setMessage(body); + builder.setPositiveButton(positiveButton,positiveButtonListener); + builder.setNegativeButton(negativeButton, negativeButtonListener); + builder.show(); + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/package-info.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/package-info.java new file mode 100644 index 00000000000..b7b98e15b38 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/internal/util/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.internal.util; \ No newline at end of file diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/package-info.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/package-info.java new file mode 100644 index 00000000000..fc66e48970a --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core; \ No newline at end of file diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/AuthException.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/AuthException.java new file mode 100644 index 00000000000..2f093010be9 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/AuthException.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import com.amazonaws.mobile.auth.core.IdentityProvider; + +/** + * Base class for exceptions that occur during auth + */ +public class AuthException extends Exception { + protected final IdentityProvider provider; + + /** + * Constructor. + * @param provider the auth provider that was being used. + * @param ex the exception that occurred. + */ + public AuthException(final IdentityProvider provider, final Exception ex) { + super(ex); + this.provider = provider; + } + + /** + * Constructor. + * @param ex the exception that occurred. + */ + public AuthException(final Exception ex) { + this(null, ex); + } + + /** + * @return the provider that was used when the failure occurred, or null if no provider + * was being used when the auth exception occurred. + */ + public IdentityProvider getProvider() { + return provider; + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/CognitoAuthException.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/CognitoAuthException.java new file mode 100644 index 00000000000..fb558b28144 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/CognitoAuthException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import com.amazonaws.mobile.auth.core.IdentityProvider; + +/** + * Thrown when there is an error obtaining a Cognito identity using an identity provider's token + * during the start-up authentication flow or when signing in with a provider. + */ +public class CognitoAuthException extends ProviderAuthException { + /** + * Constructor. + * @param provider the provider that was used while attempting to obtain a Cognito identity. + * @param ex the exception that occurred while attempting to obtain the Cognito identity. + */ + public CognitoAuthException(final IdentityProvider provider, final Exception ex) { + super(provider, ex); + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ProviderAuthException.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ProviderAuthException.java new file mode 100644 index 00000000000..8c8fd6ed6d1 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ProviderAuthException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import com.amazonaws.mobile.auth.core.IdentityProvider; + +/** + * Thrown when there is an error with a provider during the start-up authentication + * flow or when signing in with a provider. + */ +public class ProviderAuthException extends AuthException { + /** + * Constructor. + * @param provider the provider that was used while attempting to obtain a Cognito identity. + * @param ex the exception that occurred while attempting to obtain the Cognito identity. + */ + public ProviderAuthException(final IdentityProvider provider, final Exception ex) { + super(provider, ex); + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInManager.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInManager.java new file mode 100644 index 00000000000..9a4086fff1c --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInManager.java @@ -0,0 +1,308 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; +import android.util.SparseArray; +import android.view.View; + +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.IdentityProvider; +import com.amazonaws.mobile.auth.core.SignInResultHandler; +import com.amazonaws.mobile.auth.core.internal.util.ThreadUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * The SignInManager is a singleton component, which orchestrates sign-in and sign-up flows. It is responsible for + * discovering the previously signed-in provider and that provider's credentials, as well as initializing sign-in + * buttons with the providers. + */ +public class SignInManager { + /** Log Tag */ + private static final String LOG_TAG = SignInManager.class.getSimpleName(); + + /** This map holds the class and the object of the signin providers */ + private final Map, SignInProvider> signInProviders + = new HashMap, SignInProvider>(); + + /** Holds the Singleton instance */ + private volatile static SignInManager singleton = null; + + /** Reference to user's completion handler */ + private volatile SignInResultHandler signInResultHandler; + + /** permissions handler */ + private final SparseArray providersHandlingPermissions + = new SparseArray(); + + /** + * Constructor. + */ + private SignInManager(final Context context) { + if (singleton != null) { + throw new AssertionError(); + } + + for (Class providerClass : IdentityManager.getDefaultIdentityManager().getSignInProviderClasses()) { + final SignInProvider provider; + try { + provider = providerClass.newInstance(); + } catch (final IllegalAccessException ex) { + throw new RuntimeException(ex); + } catch (final InstantiationException ex) { + throw new RuntimeException(ex); + } + provider.initialize(context, IdentityManager.getDefaultIdentityManager().getConfiguration()); + + this.signInProviders.put(providerClass, provider); + + if (provider instanceof SignInPermissionsHandler) { + final SignInPermissionsHandler handler = (SignInPermissionsHandler) provider; + providersHandlingPermissions.put(handler.getPermissionRequestCode(), handler); + } + } + + singleton = this; + } + + /** + * Gets the singleton instance of this class. + * + * @return instance + */ + public synchronized static SignInManager getInstance() { + return singleton; + } + + /** + * Gets the singleton instance of this class. + * + * @return instance + */ + public synchronized static SignInManager getInstance(final Context context) { + if (singleton == null) { + singleton = new SignInManager(context); + } + return singleton; + } + + /** + * Set the passed in SignInResultHandler + * + * @param signInResultHandler + */ + public void setResultHandler(final SignInResultHandler signInResultHandler) { + this.signInResultHandler = signInResultHandler; + } + + /** + * Retrieve the reference to SignInResultHandler + * + * @return SignInResultHandler + */ + public SignInResultHandler getResultHandler() { + return signInResultHandler; + } + + /** + * Dispose the SignInManager + */ + public synchronized static void dispose() { + singleton = null; + } + + /** + * Call getPreviouslySignedInProvider to determine if the user was left signed-in when the app + * was last running. This should be called on a background thread since it may perform file + * i/o. If the user is signed in with a provider, this will return the provider for which the + * user is signed in. Subsequently, refreshCredentialsWithProvider should be called with the + * provider returned from this method. + * + * @return false if not already signed in, true if the user was signed in with a provider. + */ + public SignInProvider getPreviouslySignedInProvider() { + + for (final SignInProvider provider : signInProviders.values()) { + // Note: This method may block. This loop could potentially be sped + // up by running these calls in parallel using an executorService. + if (provider.refreshUserSignInState()) { + return provider; + } + } + return null; + } + + private class SignInProviderResultAdapter implements SignInProviderResultHandler { + final private SignInProviderResultHandler handler; + final private Activity activity; + + private SignInProviderResultAdapter(final Activity activity, + final SignInProviderResultHandler handler) { + this.handler = handler; + this.activity = activity; + } + + private Activity getActivity() { + return activity; + } + + /** {@inheritDoc} */ + @Override + public void onSuccess(final IdentityProvider provider) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + handler.onSuccess(provider); + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onCancel(final IdentityProvider provider) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + handler.onCancel(provider); + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onError(final IdentityProvider provider, final Exception ex) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + handler.onError(provider, ex); + } + }); + } + } + + private SignInProviderResultAdapter resultsAdapter; + + /** + * Refresh Cognito credentials with a provider. Results handlers are always called on the main + * thread. + * + * @param activity the calling activity. + * @param provider the sign-in provider that was previously signed in. + * @param resultsHandler the handler to receive results for credential refresh. + */ + public void refreshCredentialsWithProvider(final Activity activity, + final IdentityProvider provider, + final SignInProviderResultHandler resultsHandler) { + + if (provider == null) { + throw new IllegalArgumentException("The sign-in provider cannot be null."); + } + + if (provider.getToken() == null) { + resultsHandler.onError(provider, + new IllegalArgumentException("Given provider not previously logged in.")); + } + + resultsAdapter = new SignInProviderResultAdapter(activity, resultsHandler); + IdentityManager.getDefaultIdentityManager().setProviderResultsHandler(resultsAdapter); + IdentityManager.getDefaultIdentityManager().federateWithProvider(provider); + } + + /** + * Sets the results handler results from sign-in with a provider. Results handlers are + * always called on the UI thread. + * + * @param activity the calling activity. + * @param resultsHandler the handler for results from sign-in with a provider. + */ + public void setProviderResultsHandler(final Activity activity, + final SignInProviderResultHandler resultsHandler) { + resultsAdapter = new SignInProviderResultAdapter(activity, resultsHandler); + // Set the final results handler with the identity manager. + IdentityManager.getDefaultIdentityManager().setProviderResultsHandler(resultsAdapter); + } + + /** + * Call initializeSignInButton to initialize the logic for sign-in for a specific provider. + * + * @param providerType the SignInProvider class. + * @param buttonView the view for the button associated with this provider. + * @return the onClickListener for the button to be able to override the listener. + */ + public View.OnClickListener initializeSignInButton(final Class providerClass, + final View buttonView) { + final SignInProvider provider = findProvider(providerClass); + + // Initialize the sign in button with the identity manager's results adapter. + return provider.initializeSignInButton(resultsAdapter.getActivity(), + buttonView, IdentityManager.getDefaultIdentityManager().getResultsAdapter()); + } + + private SignInProvider findProvider(final Class providerClass) { + + final SignInProvider provider = signInProviders.get(providerClass); + + if (provider == null) { + throw new IllegalArgumentException("No such provider : " + providerClass.getName()); + } + + return provider; + } + + /** + * Handle the Activity result for login providers. + * + * @param requestCode the request code. + * @param resultCode the result code. + * @param data result intent. + * @return true if the sign-in manager handle the result, otherwise false. + */ + public boolean handleActivityResult(final int requestCode, + final int resultCode, + final Intent data) { + for (final SignInProvider provider : signInProviders.values()) { + if (provider.isRequestCodeOurs(requestCode)) { + provider.handleActivityResult(requestCode, resultCode, data); + return true; + } + } + + return false; + } + + /** + * Handle the Activity request permissions result for sign-in providers. + * + * @param requestCode the request code. + * @param permissions the permissions requested. + * @param grantResults the grant results. + */ + public void handleRequestPermissionsResult(final int requestCode, + final String permissions[], + final int[] grantResults) { + final SignInPermissionsHandler handler = providersHandlingPermissions.get(requestCode); + if (handler != null) { + handler.handleRequestPermissionsResult(requestCode, permissions, grantResults); + } + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInPermissionsHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInPermissionsHandler.java new file mode 100644 index 00000000000..35afb19946f --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInPermissionsHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +/** + * Interface for handling permissions + */ +public interface SignInPermissionsHandler { + /** + * @return the permission request code that will be used by this provider. + */ + int getPermissionRequestCode(); + + /** + * Handler for permissions results. + * + * @param requestCode the permissions request code + * @param permissions the permissions requested + * @param grantResults the results. + */ + void handleRequestPermissionsResult(int requestCode, String permissions[], + int[] grantResults); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProvider.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProvider.java new file mode 100644 index 00000000000..0f348f3982c --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import android.app.Activity; +import android.content.Intent; +import android.view.View; + +import com.amazonaws.mobile.auth.core.IdentityProvider; + +/** + * Each sign-in provider implements this interface, in order to do sign-in button + * initialization and to handle activity results that have been passed back to the + * app, after a sign-in provider window has been dismissed. + */ +public interface SignInProvider extends IdentityProvider { + /** + * Call isRequestCodeOurs to determine if this provider should handle an activity result. + * @param requestCode the requestCode from a previous call to onActivityResult. + * @return true if the request code is from this provider, otherwise false. + */ + boolean isRequestCodeOurs(int requestCode); + + /** + * Call handleActivityResult to handle the activity result. + * @param requestCode the request code. + * @param resultCode the result code. + * @param data the result intent. + */ + void handleActivityResult(int requestCode, int resultCode, Intent data); + + /** + * Initialize the sign-in button for the sign-in activity. + * @param signInActivity the activity for sign-in. + * @param buttonView the view for the sign-in button to initialize. + * @param resultsHandler the resultsHandler for provider sign-in. + * @return the onClickListener for the button to be able to override the listener, + * and null if the button cannot be initialized. + */ + View.OnClickListener initializeSignInButton(Activity signInActivity, + View buttonView, + SignInProviderResultHandler resultsHandler); +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProviderResultHandler.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProviderResultHandler.java new file mode 100644 index 00000000000..a7bd5d921dd --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/SignInProviderResultHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin; + +import com.amazonaws.mobile.auth.core.IdentityProvider; + +/** + * Implement this interface to get callbacks for the results to a sign-in operation. + */ +public interface SignInProviderResultHandler { + + /** + * 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); +} \ No newline at end of file diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/DisplayUtils.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/DisplayUtils.java new file mode 100644 index 00000000000..60a9f1a85a6 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/DisplayUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin.ui; + +import android.content.res.Resources; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.shapes.RoundRectShape; +import android.graphics.drawable.shapes.Shape; +import android.util.DisplayMetrics; + +/** + * A class containing UI Utility methods. + */ +public class DisplayUtils { + private static final DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics(); + private static int dpMultiplier = (metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT); + + /** + * @param dp number of design pixels. + * @return number of pixels corresponding to the desired design pixels. + */ + public static int dp(final int dp) { + return dp * dpMultiplier; + } + + /** + * Create a rounded rectangle with a specified corner radius. + * + * @param cornerRadius the corner radius in pixels + * @return the shape drawable. + */ + public static Shape getRoundedRectangleShape(int cornerRadius) { + + // Background color for Button. + return new RoundRectShape( + new float[]{ + cornerRadius, cornerRadius, cornerRadius, cornerRadius, + cornerRadius, cornerRadius, cornerRadius, cornerRadius}, + null, null); + } + + public static ShapeDrawable getRoundedRectangleBackground(int cornerRadius, int backgroundColor) { + final ShapeDrawable drawable = new ShapeDrawable( + getRoundedRectangleShape(cornerRadius)); + drawable.getPaint().setColor(backgroundColor); + return drawable; + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/SplitBackgroundDrawable.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/SplitBackgroundDrawable.java new file mode 100644 index 00000000000..6c453c86bc1 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/SplitBackgroundDrawable.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin.ui; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Provides drawable for a vertically split background. + */ +public class SplitBackgroundDrawable extends Drawable { + private Paint paint; + private int distanceFromTopToSplitPoint = -1; + private int topBackgroundColor; + private static final int DEFAULT_BACKGROUND_COLOR = Color.WHITE; + + public SplitBackgroundDrawable(int distanceFromTop) { + paint = new Paint(); + this.topBackgroundColor = DEFAULT_BACKGROUND_COLOR; + setSplitPointDistanceFromTop(distanceFromTop); + } + + public SplitBackgroundDrawable(int distanceFromTop, int topBackgroundColor) { + paint = new Paint(); + this.topBackgroundColor = topBackgroundColor; + setSplitPointDistanceFromTop(distanceFromTop); + } + + public void setSplitPointDistanceFromTop(int distanceFromTop) { + distanceFromTopToSplitPoint = distanceFromTop; + invalidateSelf(); + } + + @Override + public void draw(@NonNull final Canvas canvas) { + final Rect b = getBounds(); + paint.setColor(this.topBackgroundColor); + float y = distanceFromTopToSplitPoint < b.height() ? distanceFromTopToSplitPoint : b.height(); + + canvas.drawRect(0, 0, b.width(), y, paint); + paint.setColor(DEFAULT_BACKGROUND_COLOR); + canvas.drawRect(0, y, b.width(), b.height(), paint); + } + + @Override + public void setAlpha(final int alpha) { + } + + @Override + public void setColorFilter(@Nullable final ColorFilter colorFilter) { + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButton.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButton.java new file mode 100644 index 00000000000..e0aa1327413 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButton.java @@ -0,0 +1,316 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin.ui.buttons; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.RectF; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; +import android.support.annotation.Nullable; +import android.text.TextPaint; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.amazonaws.mobile.auth.core.R; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.getRoundedRectangleBackground; + +/** + * Base class for Sign in Buttons + */ +public class SignInButton extends LinearLayout { + /** Image left margin. */ + private static final int IMAGE_LEFT_MARGIN = dp(8); + + /** Image right margin. */ + private static final int IMAGE_RIGHT_MARGIN = dp(8); + + /** Text left margin. */ + private static final int TEXT_LEFT_MARGIN = dp(2); + + /** Text right margin. */ + private static final int TEXT_RIGHT_MARGIN = dp(8); + + /** Min text size in SP. */ + private static final float MIN_TEXT_SIZE_SP = 8; + + /** Max text size in pixels. */ + private static final float MAX_TEXT_SIZE_PX = dp(50); + + /** Color for the border. */ + private static final int BORDER_COLOR = 0xFF000000; + + /** Button Attributes. */ + private final SignInButtonAttributes attributes; + + /** Image View for displaying the Icon. */ + protected ImageView imageView; + + /** Text View for displaying the text */ + protected TextView textView; + + /** Bitmap for the icon. */ + protected Bitmap bitmap; + + /** Boolean to keep track of whether the button should only display the image and no text. */ + protected boolean isSmallStyle = false; + + public SignInButton(final Context context, @Nullable final AttributeSet attrs, + final int defStyleAttr, final SignInButtonAttributes buttonAttributes) { + super(context, attrs, defStyleAttr); + this.attributes = buttonAttributes; + setFocusable(true); + setClickable(true); + this.setOrientation(HORIZONTAL); + this.setGravity(Gravity.CENTER_VERTICAL); + this.setBackgroundDrawable(getBackgroundStatesDrawable()); + + imageView = new ImageView(context); + bitmap = BitmapFactory.decodeResource(getResources(), attributes.getImageIconResourceId()); + final BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap); + imageView.setImageDrawable(bitmapDrawable); + imageView.setScaleType(ImageView.ScaleType.FIT_XY); + imageView.setAdjustViewBounds(true); + + final LinearLayout.LayoutParams imageLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + imageLayoutParams.setMargins(IMAGE_LEFT_MARGIN, 0, IMAGE_RIGHT_MARGIN, 0); + + imageLayoutParams.weight = 0; + this.addView(imageView, imageLayoutParams); + + textView = new TextView(context); + textView.setTextColor(attributes.getTextColor()); + textView.setTypeface(null, Typeface.BOLD); + textView.setSingleLine(true); + textView.setGravity(Gravity.CENTER_VERTICAL); // Gravity.CENTER_HORIZONTAL + final String buttonText; + if (attrs != null) { + // Get styled attributes for the button style and button text. + final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.SignInButton); + if (styledAttributes.getInt(R.styleable.SignInButton_button_style, 0) > 0) { + isSmallStyle = true; + } + buttonText = styledAttributes.getString(R.styleable.SignInButton_text); + } else { + buttonText = null; + } + + if (buttonText != null) { + textView.setText(buttonText); + } else { + textView.setText(attributes.getDefaultTextResourceId()); + } + + final LinearLayout.LayoutParams textViewLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + textViewLayoutParams.setMargins(dp(TEXT_LEFT_MARGIN), 0, dp(TEXT_RIGHT_MARGIN), 0); + + // Use layout weight so the text view will take up the available space in the view. + textViewLayoutParams.weight = 1; + this.addView(textView, textViewLayoutParams); + + updateStyle(); + invalidate(); + } + + /** + * Create the button background. + * @param buttonFaceColor the color for the button. + * @return the background drawable. + */ + private Drawable getButtonBackground(final int buttonFaceColor) { + final int cornerRadius = attributes.getCornerRadius(); + // Set Button shape and background color. + final ShapeDrawable insetBackgroundDrawable = + getRoundedRectangleBackground(cornerRadius, buttonFaceColor); + + // Top Shadow for Button. + final GradientDrawable outerShadowTopDrawable = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, + new int[]{attributes.getTopShadowColor(), attributes.getTopShadowColor()}); + outerShadowTopDrawable.setCornerRadius(dp(cornerRadius)); + + // Bottom Shadow for Button. + final GradientDrawable outerShadowBottomDrawable = new GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, new int[]{ + attributes.getBottomShadowColor(), attributes.getBottomShadowColor()}); + outerShadowBottomDrawable.setCornerRadius(dp(cornerRadius)); + + final GradientDrawable border = new GradientDrawable(); + border.setColor(BORDER_COLOR); + border.setCornerRadius(dp(cornerRadius)); + + final LayerDrawable layerDrawable = new LayerDrawable( + new Drawable[] {border, + outerShadowTopDrawable, + outerShadowBottomDrawable, + insetBackgroundDrawable}); + + // Border for the button. + layerDrawable.setLayerInset(0, 0, 0, 0, 0); + + // Top shadow is the furthest down drawable, so it is ok if this overlaps the bottom shadow. + layerDrawable.setLayerInset(1, 0, 0, 0, 0); + + // Bottom shadow does not overlap the top shadow. + layerDrawable.setLayerInset(2, attributes.getTopShadowThickness(), attributes.getTopShadowThickness(), 0, 0); + + // Background must not overlap either of the shadows. + layerDrawable.setLayerInset(3, attributes.getTopShadowThickness(), attributes.getTopShadowThickness(), + attributes.getBottomShadowThickness(), attributes.getBottomShadowThickness()); + + return layerDrawable; + } + + /** + * @return the button background drawable states for when pressed and not pressed. + */ + private Drawable getBackgroundStatesDrawable() { + final StateListDrawable states = new StateListDrawable(); + states.addState(new int[] {android.R.attr.state_pressed}, + getButtonBackground(attributes.getBackgroundColorPressed())); + states.addState(new int[] {}, + getButtonBackground(attributes.getBackgroundColor())); + return states; + } + + private void updateStyle() { + if (isSmallStyle) { + textView.setVisibility(GONE); + this.setGravity(Gravity.CENTER); + } else { + textView.setVisibility(VISIBLE); + this.setGravity(Gravity.CENTER_VERTICAL); + } + } + + /** + * Sets the button style to small, where only the icon will be shown. + * @param shouldSetStyleSmall true if style should be small, otherwise false. + */ + public void setSmallStyle(final boolean shouldSetStyleSmall) { + isSmallStyle = shouldSetStyleSmall; + updateStyle(); + } + + /** + * Set the button text. + * @param text the text string. + */ + public void setButtonText(final String text) { + textView.setText(text); + resizeButtonText(); + } + + /** + * Set the button text from a resource. + * @param resId the resource id containing a string. + */ + public void setButtonText(final int resId) { + textView.setText(resId); + resizeButtonText(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams(); + int sideSize = (int)(getMeasuredHeight() * 0.72); + if (sideSize > bitmap.getHeight()) { + sideSize = bitmap.getHeight(); + } + + // Set image to a square based on the desired height. + layoutParams.height = sideSize; + layoutParams.width = sideSize; + } + + private boolean doesTextViewFit(final float suggestedSize, final RectF availableRect) { + final TextPaint textPaint = new TextPaint(textView.getPaint()); + textPaint.setTextSize(suggestedSize); + final TransformationMethod transformMethod = textView.getTransformationMethod(); + final String text = (transformMethod == null) ? textView.getText().toString() + : transformMethod.getTransformation(textView.getText(), textView).toString(); + + final RectF textRect = new RectF(0, 0, textPaint.measureText(text), textPaint.getFontSpacing()); + + // Return true if the text view fits, even though it may have extra space. + return availableRect.contains(textRect); + } + + private float findBestSize(final float start, final float end, final RectF availableSpace) { + float low = start; + float high = end; + float midpoint; + float bestFit = low; + // Binary search to find the best size. + while (low <= high) { + midpoint = (low + high) / 2; + if (doesTextViewFit(midpoint, availableSpace)) { + bestFit = midpoint; + low = midpoint + 0.5f; + } else { + high = midpoint - 0.5f; + } + } + return bestFit; + } + + /** + * Resize the text to the best fit. + */ + private void resizeButtonText() { + if (getMeasuredWidth() == 0 || isSmallStyle) { + return; + } + + final float minTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, MIN_TEXT_SIZE_SP, + getResources().getDisplayMetrics()); + final RectF availableSpaceRect = new RectF(); + availableSpaceRect.right = textView.getMeasuredWidth() - textView.getCompoundPaddingLeft() + - textView.getCompoundPaddingRight(); + availableSpaceRect.bottom = textView.getMeasuredHeight() - textView.getCompoundPaddingBottom() + - textView.getCompoundPaddingTop(); + + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, + findBestSize(minTextSize, MAX_TEXT_SIZE_PX, availableSpaceRect)); + } + + @Override + protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) { + super.onSizeChanged(width, height, oldwidth, oldheight); + if (width != oldwidth || height != oldheight) + resizeButtonText(); + } +} diff --git a/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButtonAttributes.java b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButtonAttributes.java new file mode 100644 index 00000000000..d193d0e60c8 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/java/com/amazonaws/mobile/auth/core/signin/ui/buttons/SignInButtonAttributes.java @@ -0,0 +1,124 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.core.signin.ui.buttons; + +/** + * Sign-in Button Attributes + */ +public class SignInButtonAttributes { + private int cornerRadius; + private int backgroundColor; + private int backgroundColorPressed; + private int topShadowColor; + private int bottomShadowColor; + private int topShadowThickness; + private int bottomShadowThickness; + private int textColor; + private int defaultTextResourceId; + private int imageIconResourceId; + + public int getCornerRadius() { + return cornerRadius; + } + + public int getBackgroundColor() { + return backgroundColor; + } + + public int getBackgroundColorPressed() { + return backgroundColorPressed; + } + + public int getTopShadowColor() { + return topShadowColor; + } + + public int getBottomShadowColor() { + return bottomShadowColor; + } + + public int getTopShadowThickness() { + return topShadowThickness; + } + + public int getBottomShadowThickness() { + return bottomShadowThickness; + } + + public int getTextColor() { + return textColor; + } + + public int getDefaultTextResourceId() { + return defaultTextResourceId; + } + + public int getImageIconResourceId() { + return imageIconResourceId; + } + + public SignInButtonAttributes withCornerRadius(int cornerRadius) { + this.cornerRadius = cornerRadius; + return this; + } + + public SignInButtonAttributes withBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + return this; + } + + public SignInButtonAttributes withBackgroundColorPressed(int backgroundColorPressed) { + this.backgroundColorPressed = backgroundColorPressed; + return this; + } + + public SignInButtonAttributes withTopShadowColor(int topShadowColor) { + this.topShadowColor = topShadowColor; + return this; + } + + public SignInButtonAttributes withBottomShadowColor(int bottomShadowColor) { + this.bottomShadowColor = bottomShadowColor; + return this; + } + + public SignInButtonAttributes withTopShadowThickness(int topShadowThickness) { + this.topShadowThickness = topShadowThickness; + return this; + } + + public SignInButtonAttributes withBottomShadowThickness(int bottomShadowThickness) { + this.bottomShadowThickness = bottomShadowThickness; + return this; + } + + public SignInButtonAttributes withTextColor(int textColor) { + this.textColor = textColor; + return this; + } + + public SignInButtonAttributes withDefaultTextResourceId(int defaultTextResourceId) { + this.defaultTextResourceId = defaultTextResourceId; + return this; + } + + public SignInButtonAttributes withImageIconResourceId(int imageIconResourceId) { + this.imageIconResourceId = imageIconResourceId; + return this; + } +} diff --git a/aws-android-sdk-auth-core/src/main/res/values/attrs.xml b/aws-android-sdk-auth-core/src/main/res/values/attrs.xml new file mode 100644 index 00000000000..9587cca4043 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/res/values/attrs.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/aws-android-sdk-auth-core/src/main/res/values/strings.xml b/aws-android-sdk-auth-core/src/main/res/values/strings.xml new file mode 100644 index 00000000000..428e7be3bb9 --- /dev/null +++ b/aws-android-sdk-auth-core/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + "Sign-in with %s canceled." + Sign-In Error + Sign-in with %1$s failed.\n%2$s + diff --git a/aws-android-sdk-auth-facebook/pom.xml b/aws-android-sdk-auth-facebook/pom.xml new file mode 100644 index 00000000000..036648f7d9d --- /dev/null +++ b/aws-android-sdk-auth-facebook/pom.xml @@ -0,0 +1,99 @@ + + 4.0.0 + com.amazonaws + aws-android-sdk-auth-facebook + aar + AWS SDK for Android - AWS Facebook SignIn + The AWS Android SDK for AWS Facebook SignIn that holds the client classes used for enabling communication with Facebook SignIn + http://aws.amazon.com/sdkforandroid + + + + UTF-8 + + + UTF-8 + + + + + com.amazonaws + aws-android-sdk-pom + 2.6.0 + + + + + android-support + file://${env.ANDROID_HOME}/extras/android/m2repository/ + + + + + + com.amazonaws + aws-android-sdk-auth-core + false + 2.6.0 + aar + + + com.google.android + android + 4.1.1.4 + provided + + + + + com.facebook.android + facebook-android-sdk + 4.1.0 + false + aar + + + com.android.support + support-v4 + + + + + + + com.android.support + support-v4 + 21.0.0 + aar + false + + + + + + + com.simpligility.maven.plugins + android-maven-plugin + 4.5.0 + true + + + 25 + 19.1.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/aws-android-sdk-auth-facebook/src/main/AndroidManifest.xml b/aws-android-sdk-auth-facebook/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..29bf920341a --- /dev/null +++ b/aws-android-sdk-auth-facebook/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookButton.java b/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookButton.java new file mode 100644 index 00000000000..8049a73ee12 --- /dev/null +++ b/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookButton.java @@ -0,0 +1,103 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.facebook; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; + +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButton; +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButtonAttributes; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; + +/** + * A Facebook button that will render appropriately for different sizes. By default, the button + * will display showing an icon and text. The button can be set to small style to show only + * the icon. + */ +public class FacebookButton extends SignInButton { + + /** Log tag. */ + private static final String LOG_TAG = FacebookButton.class.getSimpleName(); + + /** Button corner radius. */ + private static final int CORNER_RADIUS = dp(4); + + /** Button background color. */ + private static final int FB_BACKGROUND_COLOR = 0xFF3C5C95; + + /** Button background color when pressed. */ + private static final int FB_BACKGROUND_COLOR_PRESSED = 0xFF2D4570; + + /** + * Constructor. + * @param context context. + */ + public FacebookButton(@NonNull final Context context) { + this(context, null); + } + + /** + * Constructor. + * @param context context. + * @param attrs attribute set. + */ + public FacebookButton(@NonNull final Context context, + @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor. + * @param context context. + * @param attrs attribute set. + * @param defStyleAttr default style attribute. + */ + public FacebookButton(@NonNull final Context context, + @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr, + new SignInButtonAttributes() + .withCornerRadius(CORNER_RADIUS) + .withBackgroundColor(FB_BACKGROUND_COLOR) + .withBackgroundColorPressed(FB_BACKGROUND_COLOR_PRESSED) + .withTextColor(Color.WHITE) + .withDefaultTextResourceId(R.string.default_facebook_button_text) + .withImageIconResourceId(R.drawable.facebook_icon) + ); + + + if (isInEditMode()) { + return; + } + + try { + final SignInManager signInManager = SignInManager.getInstance(); + signInManager.initializeSignInButton(FacebookSignInProvider.class, this); + } catch (Exception exception) { + exception.printStackTrace(); + Log.e(LOG_TAG, "Cannot initialize the SignInButton. Please check if IdentityManager : " + + " startUpAuth and setUpToAuthenticate are invoked"); + } + } +} diff --git a/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookSignInProvider.java b/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookSignInProvider.java new file mode 100644 index 00000000000..8d273fa6083 --- /dev/null +++ b/aws-android-sdk-auth-facebook/src/main/java/com/amazonaws/mobile/auth/facebook/FacebookSignInProvider.java @@ -0,0 +1,292 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.facebook; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.support.annotation.NonNull; +import android.util.Base64; +import android.util.Log; +import android.view.View; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobile.auth.core.signin.SignInProviderResultHandler; +import com.amazonaws.mobile.auth.core.signin.SignInProvider; +import com.amazonaws.mobile.auth.core.internal.util.ThreadUtils; + +import com.facebook.AccessToken; +import com.facebook.AccessTokenTracker; +import com.facebook.CallbackManager; +import com.facebook.FacebookCallback; +import com.facebook.FacebookException; +import com.facebook.FacebookSdk; +import com.facebook.login.LoginManager; +import com.facebook.login.LoginResult; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.Locale; + +/** + * Sign-in provider for Facebook. + */ +public class FacebookSignInProvider implements SignInProvider { + + /** Log tag. */ + private static final String LOG_TAG = FacebookSignInProvider.class.getSimpleName(); + + /** The Cognito login key for Facebook to be used in the Cognito login Map. */ + public static final String COGNITO_LOGIN_KEY_FACEBOOK = "graph.facebook.com"; + + /** Timeout for refreshing the Facebook Token. */ + private static final long REFRESH_TOKEN_TIMEOUT_SECONDS = 15; + + /** Facebook's callback manager. */ + private CallbackManager facebookCallbackManager; + + /** Latch to ensure Facebook SDK is initialized before attempting to read the authorization token. */ + private final CountDownLatch initializedLatch = new CountDownLatch(1); + + /** AWS Configuration Object. */ + private AWSConfiguration awsConfiguration = null; + + /** list to store permissions. **/ + private static ArrayList permissions = new ArrayList(); + + /** + * Intitializes the SDK and debug logs the app KeyHash that must be set up with + * the facebook backend to allow login from the app. + * + * @param context the context. + * @param configuration the AWS Configuration. + */ + @Override + public void initialize(@NonNull final Context context, + final AWSConfiguration awsConfig) { + this.awsConfiguration = awsConfig; + if (!FacebookSdk.isInitialized()) { + Log.d(LOG_TAG, "Initializing Facebook SDK..."); + FacebookSdk.sdkInitialize(context); + } + initializedLatch.countDown(); + Log.d(LOG_TAG, "Facebook SDK initialization completed"); + } + + /** + * Get the SignedIn Access token. + * @return the Facebook AccessToken when signed-in with a non-expired token. + */ + private AccessToken getSignedInToken() { + try { + initializedLatch.await(); + } catch (final InterruptedException ex) { + Log.d(LOG_TAG, "Unexpected interrupt."); + } + final AccessToken accessToken = AccessToken.getCurrentAccessToken(); + if (accessToken != null && !accessToken.isExpired()) { + Log.d(LOG_TAG, "Facebook Access Token is OK. Token hashcode = " + accessToken.hashCode()); + return accessToken; + } + + Log.d(LOG_TAG, "Facebook Access Token is null or expired."); + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean isRequestCodeOurs(final int requestCode) { + return FacebookSdk.isFacebookRequestCode(requestCode); + } + + /** {@inheritDoc} */ + @Override + public void handleActivityResult(final int requestCode, + final int resultCode, + final Intent data) { + facebookCallbackManager.onActivityResult(requestCode, resultCode, data); + } + + /** {@inheritDoc} */ + @Override + public View.OnClickListener initializeSignInButton(final Activity signInActivity, + final View buttonView, + final SignInProviderResultHandler resultsHandler) { + if (buttonView == null) { + throw new IllegalArgumentException("Facebook login button view not passed in."); + } + + facebookCallbackManager = CallbackManager.Factory.create(); + + LoginManager.getInstance().registerCallback(facebookCallbackManager, new FacebookCallback() { + @Override + public void onSuccess(final LoginResult loginResult) { + Log.d(LOG_TAG, "Facebook provider sign-in succeeded."); + resultsHandler.onSuccess(FacebookSignInProvider.this); + } + + @Override + public void onCancel() { + Log.d(LOG_TAG, "Facebook provider sign-in canceled."); + resultsHandler.onCancel(FacebookSignInProvider.this); + } + + @Override + public void onError(final FacebookException exception) { + Log.e(LOG_TAG, "Facebook provider sign-in error: " + exception.getMessage()); + resultsHandler.onError(FacebookSignInProvider.this, exception); + } + }); + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View view) { + LoginManager.getInstance().logInWithReadPermissions(signInActivity, + FacebookSignInProvider.permissions); + } + }; + + buttonView.setOnClickListener(listener); + return listener; + } + + /** + * Add the login permisisons needed by the application. + * The input has to be a string or array of strings, where each + * string represents a permisison. + * + * Eg: + * FacebookSignInProvider.setPermissions("public_profile"); + * FacebookSignInProvider.setPermissions("publi_profile", "email"); + * + * @param userPermissions The list of permissions required + */ + public static void setPermissions(final String... userPermissions) { + synchronized (FacebookSignInProvider.permissions) { + for (String permission : userPermissions) { + FacebookSignInProvider.permissions.add(permission); + } + } + } + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return "Facebook"; + } + + /** {@inheritDoc} */ + @Override + public String getCognitoLoginKey() { + return COGNITO_LOGIN_KEY_FACEBOOK; + } + + /** {@inheritDoc} */ + @Override + public boolean refreshUserSignInState() { + return getSignedInToken() != null; + } + + /** {@inheritDoc} */ + @Override + public String getToken() { + AccessToken accessToken = getSignedInToken(); + if (accessToken != null) { + return accessToken.getToken(); + } + return null; + } + + /** {@inheritDoc} */ + @Override + public String refreshToken() { + + AccessToken accessToken = getSignedInToken(); + // getSignedInToken() returns null if token is expired. + if (accessToken != null) { + return accessToken.getToken(); + } + + Log.i(LOG_TAG, "Facebook provider refreshing token..."); + final CountDownLatch countDownLatch = new CountDownLatch(1); + + // The constructor of the AccessTokenTracker creates a broadcast receiver that keeps this class + // alive until a broadcast is received as a result of calling refreshCurrentAccessTokenAsync() below. + final AccessTokenTracker tokenTracker = new AccessTokenTracker() { + @Override + protected void onCurrentAccessTokenChanged(final AccessToken oldAccessToken, + final AccessToken currentAccessToken) { + this.stopTracking(); + if (currentAccessToken == null) { + // We cannot refresh the token. + // The user may have revoked permissions by going to his settings and deleting your app. + // This will cause the call to fail, and the app will likely want to send the user + // back to the sign-in page. + Log.d(LOG_TAG, "Facebook token can't be refreshed, perhaps the user revoked permissions."); + } else { + Log.i(LOG_TAG, "Facebook provider token has been updated."); + } + countDownLatch.countDown(); + } + }; + + try { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + // Refreshes access token in the background and wakes up the AccessTokenTracker + // to receive the result. + AccessToken.refreshCurrentAccessTokenAsync(); + } + }); + + try { + Log.d(LOG_TAG, "Facebook provider is waiting for token update..."); + if (!countDownLatch.await(REFRESH_TOKEN_TIMEOUT_SECONDS, TimeUnit.SECONDS)) { + Log.w(LOG_TAG, "Facebook provider timed out refreshing the token."); + return null; + } + } catch (final InterruptedException ex) { + Log.w(LOG_TAG, "Unexpected Interrupt of refreshToken()", ex); + throw new RuntimeException(ex); + } + + accessToken = getSignedInToken(); + if (accessToken == null) { + Log.w(LOG_TAG, "Facebook provider could not refresh the token."); + return null; + } + } finally { + tokenTracker.stopTracking(); + } + + return accessToken.getToken(); + } + + /** {@inheritDoc} */ + @Override + public void signOut() { + Log.d(LOG_TAG, "Facebook provider signing out..."); + LoginManager.getInstance().logOut(); + } +} diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-hdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-hdpi/facebook_icon.png new file mode 100644 index 00000000000..3a414e151e6 Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-hdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-ldpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-ldpi/facebook_icon.png new file mode 100644 index 00000000000..e7439ad185a Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-ldpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-mdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-mdpi/facebook_icon.png new file mode 100644 index 00000000000..712a7a374e3 Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-mdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-tvdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-tvdpi/facebook_icon.png new file mode 100644 index 00000000000..df29c57ee45 Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-tvdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-xhdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-xhdpi/facebook_icon.png new file mode 100644 index 00000000000..11d7de8c15c Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-xhdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-xxhdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-xxhdpi/facebook_icon.png new file mode 100644 index 00000000000..0380a7f8c01 Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-xxhdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/drawable-xxxhdpi/facebook_icon.png b/aws-android-sdk-auth-facebook/src/main/res/drawable-xxxhdpi/facebook_icon.png new file mode 100644 index 00000000000..9f44cc94c13 Binary files /dev/null and b/aws-android-sdk-auth-facebook/src/main/res/drawable-xxxhdpi/facebook_icon.png differ diff --git a/aws-android-sdk-auth-facebook/src/main/res/values/strings.xml b/aws-android-sdk-auth-facebook/src/main/res/values/strings.xml new file mode 100644 index 00000000000..6878a786479 --- /dev/null +++ b/aws-android-sdk-auth-facebook/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Continue with Facebook + diff --git a/aws-android-sdk-auth-google/pom.xml b/aws-android-sdk-auth-google/pom.xml new file mode 100644 index 00000000000..b543280a98c --- /dev/null +++ b/aws-android-sdk-auth-google/pom.xml @@ -0,0 +1,97 @@ + + 4.0.0 + com.amazonaws + aws-android-sdk-auth-google + aar + AWS SDK for Android - AWS Google SignIn + The AWS Android SDK for AWS Google SignIn that holds the client classes that are used for enabling communication with Google SignIn + http://aws.amazon.com/sdkforandroid + + + + UTF-8 + + + UTF-8 + + + + + com.amazonaws + aws-android-sdk-pom + 2.6.0 + + + + + android-support + file://${env.ANDROID_HOME}/extras/android/m2repository/ + + + google-android-gms + file://${env.ANDROID_HOME}/extras/google/m2repository/ + + + + + + com.amazonaws + aws-android-sdk-auth-core + false + 2.6.0 + aar + + + + com.google.android + android + 4.1.1.4 + provided + + + + com.android.support + support-v4 + 24.2.0 + false + aar + + + + com.google.android.gms + play-services-auth + 9.8.0 + false + aar + + + + + + + com.simpligility.maven.plugins + android-maven-plugin + 4.5.0 + true + + + 25 + 19.1.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + + diff --git a/aws-android-sdk-auth-google/src/main/AndroidManifest.xml b/aws-android-sdk-auth-google/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..1950a7dc0e7 --- /dev/null +++ b/aws-android-sdk-auth-google/src/main/AndroidManifest.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleButton.java b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleButton.java new file mode 100644 index 00000000000..71e45733682 --- /dev/null +++ b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleButton.java @@ -0,0 +1,113 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.google; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; + +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButton; +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButtonAttributes; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; + +/** + * Represents the Google SignInButton. + */ +public class GoogleButton extends SignInButton { + + /** Log tag. */ + private static final String LOG_TAG = GoogleButton.class.getSimpleName(); + + /** Button corner radius. */ + private static final int CORNER_RADIUS = dp(4); + + /** Button background color. */ + private static final int GOOGLE_BACKGROUND_COLOR = Color.WHITE; + + /** Button background color when pressed. */ + private static final int GOOGLE_BACKGROUND_COLOR_PRESSED = Color.LTGRAY; + + /** Text Color. */ + private static final int TEXT_COLOR = Color.DKGRAY; + + /** Button top shadow thickness in pixels. */ + private static final int BUTTON_TOP_SHADOW_THICKNESS = (int) dp(3); + + /** Button bottom shadow thickness in pixels. */ + private static final int BUTTON_BOTTOM_SHADOW_THICKNESS = (int) dp(3); + + /** + * Constructor. + * @param context The activity context + */ + public GoogleButton(@NonNull final Context context) { + this(context, null); + } + + /** + * Constructor. + * @param context The activity context + * @param attrs The AttributeSet passed in by the application + */ + public GoogleButton(@NonNull final Context context, + @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructor. + * @param context The activity context + * @param attrs The AttributeSet passed in by the application + * @param defStyleAttr The default style attribute + */ + public GoogleButton(@NonNull final Context context, + @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr, + new SignInButtonAttributes() + .withCornerRadius(CORNER_RADIUS) + .withBackgroundColor(GOOGLE_BACKGROUND_COLOR) + .withBackgroundColorPressed(GOOGLE_BACKGROUND_COLOR_PRESSED) + .withTextColor(TEXT_COLOR) + .withDefaultTextResourceId(R.string.default_google_button_text) + .withImageIconResourceId(R.drawable.google_icon) + .withTopShadowThickness(BUTTON_TOP_SHADOW_THICKNESS) + .withBottomShadowThickness(BUTTON_BOTTOM_SHADOW_THICKNESS) + ); + + if (isInEditMode()) { + return; + } + + try { + final SignInManager signInManager = SignInManager.getInstance(); + signInManager.initializeSignInButton(GoogleSignInProvider.class, this); + } catch (Exception exception) { + exception.printStackTrace(); + Log.e(LOG_TAG, "Cannot initialize the SignInButton. Please check if IdentityManager :" + + " startUpAuth and setUpToAuthenticate are invoked"); + } + } + +} + diff --git a/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInException.java b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInException.java new file mode 100644 index 00000000000..172eac646ca --- /dev/null +++ b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInException.java @@ -0,0 +1,51 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.google; + +import com.google.android.gms.auth.api.signin.GoogleSignInResult; + +/** + * Encapsulate exceptions that occurred due to a Google Sign-in failure. + */ +public class GoogleSignInException extends Exception { + + /** Message to express unavailability of SignIn status. */ + private static final String SIGN_IN_STATUS_UNAVAILBLE_MESSAGE = ""; + + /** Reference to the SignIn result. */ + private GoogleSignInResult signInResult; + + /** + * Constructor. + * @param signInResult the GoogleSignInResult. + */ + public GoogleSignInException(final GoogleSignInResult signInResult) { + super(signInResult.getStatus().getStatusMessage() != null + ? signInResult.getStatus().getStatusMessage() : signInResult.getStatus().toString()); + this.signInResult = signInResult; + } + + /** + * Get the reference to SignIn result. + * @return GoogleSignInResult containing error status information. + */ + public GoogleSignInResult getSignInResult() { + return signInResult; + } +} + diff --git a/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInProvider.java b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInProvider.java new file mode 100644 index 00000000000..3c320a6539e --- /dev/null +++ b/aws-android-sdk-auth-google/src/main/java/com/amazonaws/mobile/auth/google/GoogleSignInProvider.java @@ -0,0 +1,479 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.google; + +import android.Manifest; +import android.accounts.Account; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; +import android.support.v4.content.ContextCompat; +import android.util.Log; +import android.view.View; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobile.auth.core.signin.SignInPermissionsHandler; +import com.amazonaws.mobile.auth.core.signin.SignInProviderResultHandler; +import com.amazonaws.mobile.auth.core.signin.SignInProvider; +import com.amazonaws.mobile.auth.core.internal.util.ThreadUtils; + +import com.google.android.gms.auth.GoogleAuthException; +import com.google.android.gms.auth.GoogleAuthUtil; +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +import com.google.android.gms.auth.api.signin.GoogleSignInStatusCodes; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.OptionalPendingResult; +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.api.Status; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * Sign in Provider for Google. + */ +public class GoogleSignInProvider implements SignInProvider, SignInPermissionsHandler { + + /** Log tag. */ + private static final String LOG_TAG = GoogleSignInProvider.class.getSimpleName(); + + /** The Cognito login key for Google+ to be used in the Cognito login Map. */ + public static final String COGNITO_LOGIN_KEY_GOOGLE = "accounts.google.com"; + + /** + * Arbitrary activity request ID. You can handle this in the main activity, + * if you want to take action when a google services result is received. + */ + private static final int REQUEST_GOOGLE_PLAY_SERVICES = 1363; + + /** Request code used to invoke sign in user interactions. */ + private static final int RC_SIGN_IN = 16723; + + /** Permission Request Code (Must be < 256). */ + private static final int GET_ACCOUNTS_PERMISSION_REQUEST_CODE = 93; + + /** Client used to interact with Google APIs. */ + private GoogleApiClient mGoogleApiClient; + + /** Android context. */ + private Context context; + + /** Flag indicating sign-in is in progress. */ + private boolean signingIn = false; + + /** The sign-in results adapter from the SignInManager. */ + private SignInProviderResultHandler resultsHandler; + + /** When signed in, the signed in account, otherwise null. */ + private volatile GoogleSignInAccount signedInAccount = null; + + /** + * The auth token retrieved when signed-in. + * It is good for 6-months from the last service call. + */ + private volatile String authToken = null; + + /** Weak reference to the sign-in activity, needed during the obtain permissions flow. */ + private WeakReference activityWeakReference = null; + + /** AWSConfiguration object. */ + private AWSConfiguration awsConfiguration = null; + + /** list to store permissions. **/ + private static ArrayList loginScopeList = new ArrayList(); + + /** + * Constructor. Builds the Google Api Client. + * @param context context. + * @param configuration the AWS Configuration. + */ + @Override + public void initialize(@NonNull final Context activityContext, + final AWSConfiguration awsConfig) { + this.context = activityContext; + this.awsConfiguration = awsConfig; + Log.d(LOG_TAG, "Initializing Google SDK..."); + + GoogleSignInOptions.Builder builder = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN); + synchronized (GoogleSignInProvider.loginScopeList) { + for (String loginScope : loginScopeList) { + builder = builder.requestScopes(new Scope(loginScope)); + } + } + final GoogleSignInOptions gso = builder.requestEmail().requestProfile().build(); + Log.d(LOG_TAG, "Created Google SignInOptions."); + + // Build GoogleApiClient with access to basic profile + mGoogleApiClient = new GoogleApiClient.Builder(context) + .addApi(Auth.GOOGLE_SIGN_IN_API, gso) + .build(); + mGoogleApiClient.connect(); + Log.d(LOG_TAG, "Connected to the Google SignIn API Client."); + } + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return "Google"; + } + + /** {@inheritDoc} */ + @Override + public boolean refreshUserSignInState() { + final OptionalPendingResult opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient); + + if (opr.isDone()) { + // If the user's cached credentials are valid, the OptionalPendingResult will be "done" + // and the GoogleSignInResult will be available instantly. + + final GoogleSignInResult result = opr.get(); + if (result == null) { + Log.d(LOG_TAG, "GoogleSignInResult is null. Not signed-in with Google."); + return false; + } + + return handleGoogleSignInResultForIsUserSignedIn(result); + } + + final GoogleApiAvailability api = GoogleApiAvailability.getInstance(); + final int code = api.isGooglePlayServicesAvailable(context.getApplicationContext()); + if (ConnectionResult.SUCCESS == code) { + // If the user has not previously signed in on this device or the sign-in has expired, + // this asynchronous branch will attempt to sign in the user silently. Cross-device + // single sign-on will occur in this branch. + final GoogleSignInResult googleSignInResult = opr.await(); + return handleGoogleSignInResultForIsUserSignedIn(googleSignInResult); + } + + Log.w(LOG_TAG, "Google Play Services are not available. Assuming not signed-in with Google."); + return false; + } + + /** + * Add the login permisisons needed by the application. + * The input has to be a list of strings, where each + * string represents a permisison. + * + * Eg: + * GoogleSignInProvider.setPermissions(Scopes.EMAIL); + * GoogleSignInProvider.setPermissions(Scopes.EMAIL, + * Scopes.PROFILE); + * @param loginScopes The list of permissions required + */ + public static void setPermissions(final String... loginScopes) { + synchronized (GoogleSignInProvider.loginScopeList) { + for (String scope : loginScopes) { + GoogleSignInProvider.loginScopeList.add(scope); + } + } + } + + /** {@inheritDoc} */ + @Override + public String getCognitoLoginKey() { + return COGNITO_LOGIN_KEY_GOOGLE; + } + + /** {@inheritDoc} */ + @Override + public String getToken() { + return authToken; + } + + /** {@inheritDoc} */ + @Override + public String refreshToken() { + Log.d(LOG_TAG, "Google provider refreshing token..."); + + try { + authToken = getGoogleAuthToken(signedInAccount.getEmail()); + } catch (final Exception ex) { + Log.w(LOG_TAG, "Failed to update Google token", ex); + authToken = null; + } + return authToken; + } + + /** {@inheritDoc} */ + @Override + public void signOut() { + Log.d(LOG_TAG, "Google provider signing out..."); + + final Status status = Auth.GoogleSignInApi.signOut(mGoogleApiClient).await(); + Log.d(LOG_TAG, "signOut:onResult:" + status); + authToken = null; + } + + /** {@inheritDoc} */ + @Override + public boolean isRequestCodeOurs(final int requestCode) { + return (requestCode == RC_SIGN_IN); + } + + /** {@inheritDoc} */ + @Override + public void handleActivityResult(final int requestCode, + final int resultCode, + final Intent data) { + if (requestCode == RC_SIGN_IN) { + signingIn = false; + + // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...); + final GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + if (result == null) { + // This should not happen based on Google's documentation. + final String errMsg = "GoogleSignInResult was null."; + Log.wtf(LOG_TAG, errMsg); + resultsHandler.onError(GoogleSignInProvider.this, new IllegalStateException(errMsg)); + return; + } + + if (!result.isSuccess()) { + // if the user canceled + if (GoogleSignInStatusCodes.SIGN_IN_CANCELLED == result.getStatus().getStatusCode()) { + resultsHandler.onCancel(GoogleSignInProvider.this); + return; + } + + // If there was a failure, forward it along. + resultsHandler.onError(GoogleSignInProvider.this, + new GoogleSignInException(result)); + } + + Log.i(LOG_TAG, "Successful GoogleSignInResult, status=" + result.getStatus().toString()); + + new Thread(new Runnable() { + @Override + public void run() { + try { + handleGoogleSignInSuccessResult(result); + Log.d(LOG_TAG, "Google provider sign-in succeeded!"); + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + resultsHandler.onSuccess(GoogleSignInProvider.this); + } + }); + } catch (final Exception ex) { + final String errMsg = "Error retrieving Google token."; + Log.e(LOG_TAG, errMsg); + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + resultsHandler.onError(GoogleSignInProvider.this, ex); + } + }); + } + } + }).start(); + } + } + + /** {@inheritDoc} */ + @Override + public View.OnClickListener initializeSignInButton(final Activity signInActivity, + final View buttonView, + final SignInProviderResultHandler providerResultsHandler) { + this.resultsHandler = providerResultsHandler; + final GoogleApiAvailability api = GoogleApiAvailability.getInstance(); + final int code = api.isGooglePlayServicesAvailable(context.getApplicationContext()); + + if (ConnectionResult.SUCCESS != code) { + if (api.isUserResolvableError(code)) { + Log.w(LOG_TAG, "Google Play services recoverable error."); + api.showErrorDialogFragment(signInActivity, code, REQUEST_GOOGLE_PLAY_SERVICES); + } else { + final boolean isDebugBuild = + (0 != (signInActivity + .getApplicationContext() + .getApplicationInfo() + .flags & ApplicationInfo.FLAG_DEBUGGABLE)); + + if (!isDebugBuild) { + buttonView.setVisibility(View.GONE); + } else { + Log.w(LOG_TAG, "Google Play Services are not available, " + + "but we are showing the Google Sign-in Button, anyway, " + + "because this is a debug build."); + } + } + return null; + } + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View v) { + if (!signingIn) { + signingIn = true; + if (getPermissionsIfNecessary(signInActivity)) { + return; + } + + initiateGoogleSignIn(signInActivity); + } + } + }; + buttonView.setOnClickListener(listener); + return listener; + } + + /** + * Get reference to the SignedIn Account. + * @return the Google SignIn Account + */ + public GoogleSignInAccount getSignedInAccount() { + return signedInAccount; + } + + /** {@inheritDoc} */ + @Override + public int getPermissionRequestCode() { + return GET_ACCOUNTS_PERMISSION_REQUEST_CODE; + } + + /** {@inheritDoc} */ + @Override + public void handleRequestPermissionsResult(final int requestCode, + final String[] permissions, + final int[] grantResults) { + try { + if (requestCode == GET_ACCOUNTS_PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && + grantResults[0] == PackageManager.PERMISSION_GRANTED) { + // The activity will always still be available when the permissions result is returned since + // it will come back on the main thread to the containing activity. + Activity activity = activityWeakReference.get(); + if (activity == null) { + throw new Exception("Cannot initiate GoogleSignIn." + + " Activity context is null"); + } + initiateGoogleSignIn(activity); + } else { + Log.i(LOG_TAG, "Permissions not granted for Google sign-in."); + signingIn = false; + } + } + } catch (final Exception exception) { + Log.e(LOG_TAG, "Cannot initiate GoogleSignIn. Check your permissions.", exception); + } + } + + private boolean handleGoogleSignInResultForIsUserSignedIn(@NonNull final GoogleSignInResult result) { + final String accountEmail; + signedInAccount = result.getSignInAccount(); + if (signedInAccount != null) { + accountEmail = signedInAccount.getEmail(); + } else { + Log.i(LOG_TAG, "GoogleSignInResult indicates not signed in with an account: " + + result.getStatus().toString()); + authToken = null; + return false; + } + + Log.d(LOG_TAG, "Google sign-in was cached, attempting to retrieve auth token."); + try { + authToken = getGoogleAuthToken(accountEmail); + return true; + } catch (final Exception ex) { + Log.w(LOG_TAG, "Couldn't obtain Google Auth token for account.", ex); + return false; + } + } + + private boolean getPermissionsIfNecessary(final Activity activity) { + if (ContextCompat.checkSelfPermission(activity.getApplicationContext(), + Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED) { + this.activityWeakReference = new WeakReference(activity); + ActivityCompat.requestPermissions(activity, + new String[]{Manifest.permission.GET_ACCOUNTS}, + GET_ACCOUNTS_PERMISSION_REQUEST_CODE); + return true; + } + + return false; + } + + private void initiateGoogleSignIn(final Activity signInActivity) { + Log.d(LOG_TAG, "Launching sign-in activity."); + final Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); + signInActivity.startActivityForResult(signInIntent, RC_SIGN_IN); + } + + private void handleGoogleSignInSuccessResult(@NonNull final GoogleSignInResult result) throws + IOException, GoogleAuthException, GoogleSignInException { + + final String accountEmail; + signedInAccount = result.getSignInAccount(); + if (signedInAccount != null) { + accountEmail = signedInAccount.getEmail(); + } else { + Log.i(LOG_TAG, "GoogleSignInResult indicates not signed in with an account."); + final GoogleSignInException ex = new GoogleSignInException(result); + Log.d(LOG_TAG, ex.getMessage(), ex); + + authToken = null; + throw ex; + } + + authToken = getGoogleAuthToken(accountEmail); + } + + private String getGoogleClientId() throws IOException { + Log.d(LOG_TAG, "Getting Google Client Id from AWSConfiguration..."); + final String clientId; + + try { + clientId = awsConfiguration.optJsonObject("GoogleSignIn").getString("ClientId-WebApp"); + Log.d(LOG_TAG, "clientId=" + clientId); + return clientId; + } catch (Exception exception) { + throw new IllegalArgumentException("Couldn't find Google ClientId from the AWSConfiguration." + + "Please check the awsconfiguration.json file", exception); + } + } + + private String getGoogleAuthToken(final String accountEmail) throws GoogleAuthException, IOException { + Log.d(LOG_TAG, "Google provider getting token..."); + + final Account googleAccount = new Account(accountEmail, GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE); + final String scopes = "audience:server:client_id:" + getGoogleClientId(); + + // Retrieve the Google token. + final String token = GoogleAuthUtil.getToken(context, googleAccount, scopes); + // UserRecoverableAuthException will be thrown from GoogleAuthUtil.getToken() if not signed in. + + if (token != null) { + Log.d(LOG_TAG, "Google Token is OK. Token hashcode = " + token.hashCode()); + } else { + Log.d(LOG_TAG, "Google Token is NULL."); + } + + return token; + } +} diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-hdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-hdpi/google_icon.png new file mode 100644 index 00000000000..b3b5961efda Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-hdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-ldpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-ldpi/google_icon.png new file mode 100644 index 00000000000..fe87c6cc921 Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-ldpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-mdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-mdpi/google_icon.png new file mode 100644 index 00000000000..0d03604dc9a Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-mdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-tvdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-tvdpi/google_icon.png new file mode 100644 index 00000000000..e3910731413 Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-tvdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-xhdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-xhdpi/google_icon.png new file mode 100644 index 00000000000..cac0b653c08 Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-xhdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-xxhdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-xxhdpi/google_icon.png new file mode 100644 index 00000000000..7b2fa33ae77 Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-xxhdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/drawable-xxxhdpi/google_icon.png b/aws-android-sdk-auth-google/src/main/res/drawable-xxxhdpi/google_icon.png new file mode 100644 index 00000000000..0c3fe17c310 Binary files /dev/null and b/aws-android-sdk-auth-google/src/main/res/drawable-xxxhdpi/google_icon.png differ diff --git a/aws-android-sdk-auth-google/src/main/res/values/strings.xml b/aws-android-sdk-auth-google/src/main/res/values/strings.xml new file mode 100644 index 00000000000..01e75496f42 --- /dev/null +++ b/aws-android-sdk-auth-google/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Sign in with Google + diff --git a/aws-android-sdk-auth-ui/pom.xml b/aws-android-sdk-auth-ui/pom.xml new file mode 100644 index 00000000000..afd91e17b7c --- /dev/null +++ b/aws-android-sdk-auth-ui/pom.xml @@ -0,0 +1,114 @@ + + 4.0.0 + com.amazonaws + aws-android-sdk-auth-ui + aar + AWS SDK for Android - AWS Authentication UI + The AWS Android SDK for Authentication UI holds the client classes that are used for presenting the SignIn Screen with SignInButtons and Amazon Cognito UserPools UI + http://aws.amazon.com/sdkforandroid + + + + UTF-8 + + + UTF-8 + + + + + com.amazonaws + aws-android-sdk-pom + 2.6.0 + + + + + android-support + file://${env.ANDROID_HOME}/extras/android/m2repository/ + + + + + + com.amazonaws + aws-android-sdk-auth-core + false + 2.6.0 + aar + + + com.amazonaws + aws-android-sdk-auth-google + true + 2.6.0 + aar + + + com.amazonaws + aws-android-sdk-auth-facebook + true + 2.6.0 + aar + + + com.amazonaws + aws-android-sdk-auth-userpools + true + 2.6.0 + aar + + + com.google.android + android + 4.1.1.4 + provided + + + com.android.support + support-v4 + 23.0.0 + aar + + + com.android.support + appcompat-v7 + 23.0.0 + aar + + + com.android.support + cardview-v7 + 23.0.0 + aar + + + + + + + com.simpligility.maven.plugins + android-maven-plugin + 4.5.0 + true + + + 25 + 19.1.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/aws-android-sdk-auth-ui/src/main/AndroidManifest.xml b/aws-android-sdk-auth-ui/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..69bb55f53a9 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/AuthUIConfiguration.java b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/AuthUIConfiguration.java new file mode 100644 index 00000000000..2fac8264f25 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/AuthUIConfiguration.java @@ -0,0 +1,204 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.ui; + +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; + +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButton; + +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; + +/** + * Stores Configuration information related to the SignIn UI screen. + */ +public final class AuthUIConfiguration { + + /** + * Key for Background Color. + */ + private static final String CONFIG_KEY_SIGN_IN_BACKGROUND_COLOR = "signInBackgroundColor"; + + /** + * Key for Resource Identifier of the Logo Image. + */ + private static final String CONFIG_KEY_SIGN_IN_IMAGE_RESOURCE_ID = "signInImageResId"; + + /** + * Key for UserPools. + */ + private static final String CONFIG_KEY_ENABLE_USER_POOLS = "signInUserPoolsEnabled"; + + /** + * Key for SignInButtons. + */ + private static final String CONFIG_KEY_SIGN_IN_BUTTONS = "signInButtons"; + + /** + * Map to store the key and the corresponding objects. + */ + private final Map config; + + /** + * Constructor. + * + * @param config The Configuration Map + */ + private AuthUIConfiguration(final Map config) { + this.config = config; + } + + /** + * Returns the resource identifier of the logo image if set by the user. + * Else, returns the resource identifier of the default logo image + * passed in. + * + * @param defaultResourceId The Resource identifier for the default logo image + * @return The resource identifier set in config or the default passed in + */ + public int getSignInImageResourceId(final int defaultResourceId) { + final Integer resId = (Integer) config.get(CONFIG_KEY_SIGN_IN_IMAGE_RESOURCE_ID); + if (resId == null) { + return defaultResourceId; + } + return resId; + } + + /** + * Returns the background color chosen by the user. + * Else, returns the default background color passed in. + * + * @param defaultBackgroundColor The Default Background color + * @return The background color set in config or the default passed in + */ + public int getSignInBackgroundColor(final int defaultBackgroundColor) { + final Integer backgroundColor = (Integer) config.get(CONFIG_KEY_SIGN_IN_BACKGROUND_COLOR); + if (backgroundColor == null) { + return defaultBackgroundColor; + } + return backgroundColor; + } + + /** + * Checks if userpools is enabled by the user. + * @return True if UserPools is enabled + */ + public boolean getSignInUserPoolsEnabled() { + Object userPoolsEnabled = config.get(CONFIG_KEY_ENABLE_USER_POOLS); + if (userPoolsEnabled != null) { + return (Boolean) userPoolsEnabled; + } else { + return false; + } + } + + /** + * Gets the list of the SignInButton classes configured. + * @return The list of SignInButton classes + */ + public ArrayList> getSignInButtons() { + return (ArrayList) config.get(CONFIG_KEY_SIGN_IN_BUTTONS); + } + + + /** + * Class for building the AWSMobileAuthUIConfiguration object + * + * For example, create the config object with specific attributes. + * + * AuthUIConfiguration config = + * new AuthUIConfiguration.Builder() + * .userPools(true) + * .logoResId(R.drawable.logo_image) + * .signInButton(CustomSignInButton.class) + * .build(); + */ + public static class Builder { + + /** Local object for storing the configuration. */ + private final Map configuration = new HashMap(); + + /** Constructor. */ + public Builder() { } + + /** + * The Resource Identifier for the logo image passed by the user + * is stored in the config map. + * + * @param logoResId The Resource identifier for the logo image + * @return builder + */ + public Builder logoResId(@DrawableRes final int logoResId) { + configuration.put(CONFIG_KEY_SIGN_IN_IMAGE_RESOURCE_ID, logoResId); + return this; + } + + /** + * The Background color that is dislayed on the first half + * of the SignIn Screen. + * + * @param color The Background color + * @return builder + */ + public Builder backgroundColor(final int color) { + configuration.put(CONFIG_KEY_SIGN_IN_BACKGROUND_COLOR, color); + return this; + } + + /** + * Invoke this method in order to enable userpools. + * + * @param enabledUserPools Flag that indicates if the userpools is enabled or not + * @return builder + */ + public Builder userPools(final boolean enabledUserPools) { + configuration.put(CONFIG_KEY_ENABLE_USER_POOLS, enabledUserPools); + return this; + } + + /** + * Add a SignInButton to the SignIn Screen by passing in the Class + * of the button that inherits from the SignInButton. + * + * @param signInButton Button Class that inherits from the SignInButton + * @return builder + */ + public Builder signInButton(@NonNull final Class signInButton) { + ArrayList> signInButtonList; + if (configuration.get(CONFIG_KEY_SIGN_IN_BUTTONS) == null) { + signInButtonList = new ArrayList>(); + signInButtonList.add(signInButton); + configuration.put(CONFIG_KEY_SIGN_IN_BUTTONS, signInButtonList); + } else { + signInButtonList = (ArrayList) configuration.get(CONFIG_KEY_SIGN_IN_BUTTONS); + signInButtonList.add(signInButton); + } + return this; + } + + /** + * Builds the AuthUIConfiguration object. + * @return the AuthUIConfiguration created by the parts provided + */ + public AuthUIConfiguration build() { + return new AuthUIConfiguration(configuration); + } + } +} diff --git a/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInActivity.java b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInActivity.java new file mode 100644 index 00000000000..97b0a187f3a --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInActivity.java @@ -0,0 +1,190 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.ui; + +import android.content.Intent; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AppCompatActivity; +import android.util.Log; + +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.IdentityProvider; +import com.amazonaws.mobile.auth.core.SignInResultHandler; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.SignInProviderResultHandler; + +import java.util.HashMap; +import java.util.UUID; + + +/** + * Activity for handling Sign-in with an Identity Provider. + */ +public class SignInActivity extends AppCompatActivity { + + /** Log Tag. */ + private static final String LOG_TAG = SignInActivity.class.getSimpleName(); + + /** Reference to the singleton instance of SignInManager. */ + private SignInManager signInManager; + + /** Stores the UI Configuration object passed into SignInActivity. */ + static HashMap configurationStore + = new HashMap(); + + /** + * SignInProviderResultHandlerImpl handles the final result from sign in. + */ + private class SignInProviderResultHandlerImpl implements SignInProviderResultHandler { + /** + * Receives the successful sign-in result and starts the main activity. + * + * @param provider the identity provider used for sign-in. + */ + @Override + public void onSuccess(final IdentityProvider provider) { + Log.i(LOG_TAG, String.format(getString(R.string.sign_in_succeeded_message_format), + provider.getDisplayName())); + + // The sign-in manager is no longer needed once signed in. + SignInManager.dispose(); + final SignInResultHandler signInResultsHandler = signInManager.getResultHandler(); + + // Call back the results handler. + signInResultsHandler.onSuccess(SignInActivity.this, provider); + finish(); + } + + /** + * Receives the sign-in result indicating the user canceled and shows a toast. + * + * @param provider the identity provider with which the user attempted sign-in. + */ + @Override + public void onCancel(final IdentityProvider provider) { + Log.i(LOG_TAG, String.format(getString(R.string.sign_in_canceled_message_format), + provider.getDisplayName())); + signInManager.getResultHandler() + .onIntermediateProviderCancel(SignInActivity.this, provider); + } + + /** + * Receives the sign-in result that an error occurred signing in and shows a toast. + * + * @param provider the identity provider with which the user attempted sign-in. + * @param ex the exception that occurred. + */ + @Override + public void onError(final IdentityProvider provider, final Exception ex) { + Log.e(LOG_TAG, String.format("Sign-in with %s caused an error.", provider.getDisplayName()), ex); + signInManager.getResultHandler() + .onIntermediateProviderError(SignInActivity.this, provider, ex); + } + } + + /** + * This method is called when SignInActivity is created. + * Get the instance of SignInManager and set the callback + * to be received from SignInManager on signin. + */ + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + signInManager = SignInManager.getInstance(); + if (signInManager == null) { + Log.e(LOG_TAG, "Invoke SignInActivity.startSignInActivity() method to create the SignInManager."); + return; + } + signInManager.setProviderResultsHandler(this, new SignInProviderResultHandlerImpl()); + setContentView(R.layout.activity_sign_in); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public void onRequestPermissionsResult(final int requestCode, + @NonNull final String[] permissions, + @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + signInManager.handleRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + protected void onActivityResult(final int requestCode, + final int resultCode, + final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + signInManager.handleActivityResult(requestCode, resultCode, data); + } + + @Override + public void onBackPressed() { + if (signInManager.getResultHandler().onCancel(this)) { + super.onBackPressed(); + // Since we are leaving sign-in via back, we can dispose the sign-in manager, since sign-in was cancelled. + SignInManager.dispose(); + } + } + + /** + * Start the SignInActivity that kicks off the authentication flow + * by initializing the SignInManager. + * + * @param context The context from which the SignInActivity will be started + * @param config Reference to AuthUIConfiguration object + */ + public static void startSignInActivity(@NonNull final Context context, + final AuthUIConfiguration config) { + try { + String uuid = UUID.randomUUID().toString(); + synchronized (configurationStore) { + configurationStore.put(uuid, config); + } + Intent intent = new Intent(context, SignInActivity.class); + intent.putExtra(SignInView.CONFIGURATION_KEY, uuid); + intent.putExtra(SignInView.BACKGROUND_COLOR_KEY, + config.getSignInBackgroundColor(SignInView.DEFAULT_BACKGROUND_COLOR)); + context.startActivity(intent); + } catch (Exception exception) { + Log.e(LOG_TAG, "Cannot start the SignInActivity. " + + "Check the context and the configuration object passed in.", exception); + } + } + + /** + * Start the SignInActivity that kicks off the authentication flow + * by initializing the SignInManager. + * + * @param context The context from which the SignInActivity will be started + */ + public static void startSignInActivity(@NonNull final Context context) { + try { + Intent intent = new Intent(context, SignInActivity.class); + context.startActivity(intent); + } catch (Exception exception) { + Log.e(LOG_TAG, "Cannot start the SignInActivity. " + + "Check the context and the configuration object passed in.", exception); + } + } +} diff --git a/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInView.java b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInView.java new file mode 100644 index 00000000000..d1fd9add12e --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/SignInView.java @@ -0,0 +1,533 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.amazonaws.mobile.auth.core.signin.ui.SplitBackgroundDrawable; +import com.amazonaws.mobile.auth.core.signin.ui.buttons.SignInButton; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.ArrayList; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; + +/** + * View for displaying sign-in components. + */ +public class SignInView extends LinearLayout { + + /** Log Tag. */ + private static final String LOG_TAG = SignInView.class.getSimpleName(); + + /** Height of the logo image. */ + private static final int MAX_IMAGE_HEIGHT = dp(250); + + /** Margins of the logo image. */ + private static final int IMAGE_MARGINS = dp(20); + + /** Margins for the Image Layout that holds the logo image. */ + private static final int IMAGE_LAYOUT_MARGINS = dp(10); + + /** String that represents the SDK Version. */ + private static final String SDK_VERSION = "2.6.0"; + + /** Common Prefix of the namespaces of different SignIn providers. */ + private static final String NAMESPACE_COMMON_PREFIX = "com.amazonaws.mobile.auth"; + + /** Group name. */ + private static final String AWS_MOBILE_AUTH_GROUP_NAME = "com.amazonaws"; + + /** Dependency name for UserPool SignIn View class. */ + private static final String USER_POOL_SIGN_IN_VIEW = NAMESPACE_COMMON_PREFIX + ".userpools.UserPoolSignInView"; + + /** Dependency name for FormView of UserPool SignIn. */ + private static final String FORM_VIEW = NAMESPACE_COMMON_PREFIX + ".userpools.FormView"; + + /** Dependency name for UserPool SignIn package. */ + private static final String USER_POOL_SIGN_IN_IMPORT = AWS_MOBILE_AUTH_GROUP_NAME + + ":aws-android-sdk-auth-userpools:" + + SDK_VERSION; + + /** Dependency name for Facebook Button class. */ + private static final String FACEBOOK_BUTTON = NAMESPACE_COMMON_PREFIX + ".facebook.buttons.FacebookButton"; + + /** Dependency name for Facebook SignIn package. */ + private static final String FACEBOOK_SIGN_IN_IMPORT = AWS_MOBILE_AUTH_GROUP_NAME + + ":aws-android-sdk-auth-facebook:" + + SDK_VERSION; + + /** Dependency name for Google Button class. */ + private static final String GOOGLE_BUTTON = NAMESPACE_COMMON_PREFIX + ".google.buttons.GoogleButton"; + + /** Dependency name for Google SignIn package. */ + private static final String GOOGLE_SIGN_IN_IMPORT = AWS_MOBILE_AUTH_GROUP_NAME + + ":aws-android-sdk-auth-google:" + + SDK_VERSION; + + /** Package Name for AuthUI. */ + private static final String PACKAGE_NAME = "com.amazonaws.mobile.auth.ui"; + + /** SignIn Background Color Key. **/ + public static final String BACKGROUND_COLOR_KEY = "signInBackgroundColor"; + + /** Configuration Key to store AuthUIConfiguration objects. */ + public static final String CONFIGURATION_KEY = "com.amazonaws.mobile.auth.ui.configurationkey"; + + /** Default Background Color. */ + public static final int DEFAULT_BACKGROUND_COLOR = Color.DKGRAY; + + /** Resource Identitifer for default Logo Image. */ + public static final int DEFAULT_LOGO_IMAGE_RES_ID = R.drawable.default_sign_in_logo; + + /** Image View. */ + private ImageView imageView; + + /** Divider in the SignIn screen. */ + private View divider; + + /** Reference to the SplitBackgroundDrawable. */ + private SplitBackgroundDrawable splitBackgroundDrawable; + + /** Margins for the SignIn Button. */ + private int signInButtonMargin; + + /** Width for the SignIn Button. */ + private int signInButtonWidth; + + /** Height for the SignIn Button. */ + private int signInButtonHeight; + + /** Reference to the AuthUIConfiguration. */ + private AuthUIConfiguration config = null; + + /** Reference to the UserPoolsSignInView. */ + private Object userPoolsSignInView = null; + + /** Stores the list of SignInButtons. */ + private ArrayList buttonStore = null; + + /** Resource Identifier for the logo image. */ + private int logoResId; + + /** Background Color. */ + private int backgroundColor; + + /** + * Consructor. + * @param context Activity Context + */ + public SignInView(@NonNull final Context context) { + this(context, null); + } + + /** + * Constructor. + * @param context Activity Context + * @param attrs Attribute Set + */ + public SignInView(@NonNull final Context context, + @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Sets up the logo image and the background color. + */ + private void setUpLogoAndBackgroundColor() { + /** + * Get default background color and image resource ids. + */ + this.logoResId = DEFAULT_LOGO_IMAGE_RES_ID; + this.backgroundColor = DEFAULT_BACKGROUND_COLOR; + + Log.d(LOG_TAG, "Using defaults: backgroundColor = " + + backgroundColor + "; logoResId = " + logoResId); + + /** + * Read in the image resource id and background color + * from the configuration if present. + */ + if (!isInEditMode()) { + if (this.config != null) { + this.logoResId = this.config.getSignInImageResourceId(logoResId); + this.backgroundColor = this.config.getSignInBackgroundColor(backgroundColor); + } + } + + Log.d(LOG_TAG, "Background Color : " + this.backgroundColor); + Log.d(LOG_TAG, "Logo : " + this.logoResId); + } + + /** + * Sets up the Splitter and background drawable. + */ + private void setUpSplitBackgroundDrawable() { + if (this.config != null && this.config.getSignInUserPoolsEnabled()) { + splitBackgroundDrawable = new SplitBackgroundDrawable(0, backgroundColor); + } else { + splitBackgroundDrawable = new SplitBackgroundDrawable(0); + } + setBackgroundDrawable(splitBackgroundDrawable); + } + + /** + * Sets up the image view that displays the logo image. + * @param context The activity context. + */ + private void setUpImageView(@NonNull final Context context) { + /** + * ImageView that holds the logo image. + */ + imageView = new ImageView(context); + imageView.setImageResource(logoResId); + imageView.setScaleType(ImageView.ScaleType.FIT_XY); + imageView.setAdjustViewBounds(true); + + /** + * Layout for the image view. + */ + final LinearLayout.LayoutParams imageLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + imageLayoutParams.setMargins(IMAGE_LAYOUT_MARGINS, 0, IMAGE_LAYOUT_MARGINS, 0); + addView(imageView, imageLayoutParams); + } + + /** + * Sets up the UserPools UI with the Email and Password FormView. + * @param context The activity context. + */ + private void setUpUserPools(@NonNull final Context context) { + /** + * Use Reflection for UserPoolSignIn dependency. + */ + if (this.config != null && this.config.getSignInUserPoolsEnabled()) { + Log.d(LOG_TAG, "Trying to create an instance of UserPoolSignInView"); + + userPoolsSignInView = createDependencyObject(USER_POOL_SIGN_IN_VIEW, context, USER_POOL_SIGN_IN_IMPORT); + if (userPoolsSignInView != null) { + final LinearLayout.LayoutParams userPoolLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + addView((View) userPoolsSignInView, userPoolLayoutParams); + } + } + } + + /** + * Sets up the divider that divides the UserPools UI and the SignInButtons. + * @param context The activity context. + */ + private void setUpDivider(@NonNull final Context context) { + /** + * Create "--or sign in with--" divider if userpools is configured. + * Else create "--sign in with--" divider. + */ + if (this.config != null && this.config.getSignInUserPoolsEnabled()) { + divider = inflate(context, R.layout.horizontal_or_sign_in_divider, null); + } else { + divider = inflate(context, R.layout.horizontal_sign_in_divider, null); + } + addView(divider); + } + + /** + * Sets up the SignIn Buttons. + * @param context The activity context. + */ + private void setUpSignInButtons(@NonNull final Context context) { + /** + * Get the height, width and margins for the sign in buttons. + */ + signInButtonMargin = getResources().getDimensionPixelSize(R.dimen.sign_in_button_margin); + signInButtonWidth = getResources().getDimensionPixelSize(R.dimen.sign_in_button_width); + signInButtonHeight = getResources().getDimensionPixelSize(R.dimen.sign_in_button_height); + + /** + * Add the signInButtons configured to the view. + */ + this.addSignInButtonsToView(context); + + /** + * There are two conditions on which the divider is set. + * + * 1. If UserPools is configured and one or more buttons are added. + * 2. If One of more buttons are added. + */ + divider.setVisibility(GONE); + if (this.buttonStore.size() > 0) { + divider.setVisibility(VISIBLE); + } + } + + /** + * Constructor. + * @param context Activity Context + * @param attrs Attribute Set + * @param defStyleAttr Default Style Attribute + */ + public SignInView(@NonNull final Context context, + @Nullable final AttributeSet attrs, + final int defStyleAttr) { + super(context, attrs, defStyleAttr); + + this.setOrientation(VERTICAL); + this.setGravity(Gravity.CENTER_HORIZONTAL); + this.buttonStore = new ArrayList(); + this.config = getConfiguration(context); + + this.setUpLogoAndBackgroundColor(); + this.setUpSplitBackgroundDrawable(); + this.setUpImageView(context); + this.setUpUserPools(context); + this.setUpDivider(context); + this.setUpSignInButtons(context); + } + + /** {@inheritDoc} */ + @Override + protected void onMeasure(final int widthMeasureSpec, + final int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + resizeImageView(); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + /** {@inheritDoc} */ + @Override + protected void onLayout(final boolean changed, + final int l, + final int t, + final int r, + final int b) { + super.onLayout(changed, l, t, r, b); + + /** Find the split point for the background image, so each half gets a different color. */ + if (this.config != null && this.config.getSignInUserPoolsEnabled()) { + if (userPoolsSignInView != null) { + View view = (View) userPoolsSignInView; + Object formViewObject = invokeGetCredentialsFormView(USER_POOL_SIGN_IN_VIEW, + userPoolsSignInView, + USER_POOL_SIGN_IN_IMPORT); + final int measuredHeight = ((View) formViewObject).getMeasuredHeight(); + final int splitPoint = view.getTop() + (measuredHeight / 2); + splitBackgroundDrawable.setSplitPointDistanceFromTop(splitPoint); + } + } else { + final int splitPoint = imageView.getTop() + imageView.getMeasuredHeight(); + splitBackgroundDrawable.setSplitPointDistanceFromTop(splitPoint); + } + } + + /** + * Resizes the image view based on the UI Configuration. + */ + private void resizeImageView() { + int availableHeight = getAvailableHeight(); + int availableWidth = getMeasuredWidth(); + + int dimension = Math.min(availableHeight, availableWidth); + imageView.getLayoutParams().height = dimension; + imageView.getLayoutParams().width = dimension; + + ((LayoutParams) imageView.getLayoutParams()).setMargins( + IMAGE_MARGINS, IMAGE_MARGINS, IMAGE_MARGINS, IMAGE_MARGINS); + imageView.setLayoutParams(imageView.getLayoutParams()); + } + + /** + * Gets the available height based on the UserPools UI and SignIn Buttons. + * @return The available height in the view + */ + private int getAvailableHeight() { + int availableHeight = getMeasuredHeight(); + + if (this.config != null && this.config.getSignInUserPoolsEnabled()) { + if (userPoolsSignInView != null) { + availableHeight -= ((View) userPoolsSignInView).getMeasuredHeight(); + } + } + + availableHeight -= divider.getMeasuredHeight(); + + final int count = this.buttonStore.size(); + if (count > 0) { + for (SignInButton button : this.buttonStore) { + final int buttonHeight = ((View) button).getMeasuredHeight(); + availableHeight -= buttonHeight; + availableHeight -= (2 * signInButtonMargin); + } + } + + /** Subtract the top and bottom image margins. */ + availableHeight -= (2 * IMAGE_MARGINS); + + /** Leave a space at least equal to the size of the sign-in button margin on the bottom of the view. */ + availableHeight -= signInButtonMargin; + + if (availableHeight > MAX_IMAGE_HEIGHT) { + availableHeight = MAX_IMAGE_HEIGHT; + } + + return availableHeight; + } + + /** + * Creates the object for the dependency class specified. + * @param className The class name + * @param methodName The method name + * @param dependency The string that represents the dependency containing the className + * @return The object returned by invoking a method on the class passed in. + */ + private Object createDependencyObject(final String className, + final Context context, + final String dependency) { + try { + Class classObject = Class.forName(className); + Constructor constructor = classObject.getConstructor(Context.class); + Object object = constructor.newInstance(new Object[] { context }); + return object; + } catch (RuntimeException runtimeException) { + throw runtimeException; + } catch (Exception exception) { + Log.e(LOG_TAG, "Couldn't construct the object. Class " + className + " is not found. " + + "Please import the appropriate dependencies: " + dependency, exception); + return null; + } + } + + /** + * Invoke FormView.getCredentialsFormView() function through reflection. + * @param className The class name + * @param viewObject The object passed as parameter + * @param dependencyImport The string that represents the dependency containing the className + * @return The object returned by invoking a method on the class passed in. + * @return The object returned by invoking FormView.getCredentialsFormView() + */ + private Object invokeGetCredentialsFormView(final String className, + final Object viewObject, + final String dependency) { + return invokeReflectedMethod(className, + "getCredentialsFormView", + viewObject, + dependency); + } + + /** + * Invoke a method of a class through reflection. + * @param className The class name + * @param methodName The method name + * @param viewObject The object passed as parameter + * @param dependencyImport The string that represents the dependency containing the className + * @return The object returned by invoking a method on the class passed in. + */ + private Object invokeReflectedMethod(final String className, + final String methodName, + final Object viewObject, + final String dependencyImport) { + + try { + Class formViewClass = Class.forName(className); + Method method = formViewClass.getMethod(methodName, new Class[] {}); + return method.invoke(viewObject); + } catch (Exception exception) { + Log.e(LOG_TAG, "Class " + className + " is not found. Method " + methodName + " is not found." + + "Please import the appropriate dependencies: " + dependencyImport, exception); + return null; + } + } + + /** + * Gets the AuthUIConfiguration from the intent passed in by the activity. + * @param context The activity context. + * @return AuthUIConfiguration The configuration object passed in by the application. + */ + private AuthUIConfiguration getConfiguration(@NonNull final Context context) { + try { + Intent intent = ((Activity) context).getIntent(); + String uuid = (String)(intent.getSerializableExtra(CONFIGURATION_KEY)); + AuthUIConfiguration configuration = SignInActivity.configurationStore.get(uuid); + purgeConfigurationStoreEntry(uuid); + return configuration; + } catch (Exception exception) { + exception.printStackTrace(); + Log.e(LOG_TAG, "Intent is null. Cannot read the configuration from the intent."); + return null; + } + } + + /** + * Remove the UUID, AuthUIConfiguration entry from the configuration store. + * @param uuid The UUID for which the entry should be removed. + */ + private void purgeConfigurationStoreEntry(final String uuid) { + try { + synchronized (SignInActivity.configurationStore) { + SignInActivity.configurationStore.remove(uuid); + } + } catch (Exception exception) { + exception.printStackTrace(); + Log.e(LOG_TAG, "Cannot purge the entry from the configuration store."); + return; + } + } + + /** + * Add SignInButtons to the view. + * @param context The activity context. + */ + private void addSignInButtonsToView(@NonNull final Context context) { + try { + if (this.config != null) { + ArrayList> signInButtons = this.config.getSignInButtons(); + for (Class signInButton : signInButtons) { + SignInButton buttonObject = (SignInButton) createDependencyObject(signInButton.getName(), + context, signInButton.getCanonicalName()); + if (buttonObject != null) { + final LinearLayout.LayoutParams signInButtonLayoutParams + = new LinearLayout.LayoutParams(this.signInButtonWidth, this.signInButtonHeight); + signInButtonLayoutParams.setMargins(0, this.signInButtonMargin, 0, this.signInButtonMargin); + this.buttonStore.add(buttonObject); + addView((View) buttonObject, signInButtonLayoutParams); + } else { + Log.e(LOG_TAG, "Cannot construct an object of SignInButton " + + signInButton.getCanonicalName()); + } + } + } else { + Log.d(LOG_TAG, "Configuration is Null. There are no buttons to add to the view"); + } + } catch (Exception exception) { + exception.printStackTrace(); + Log.e(LOG_TAG, "Cannot access the configuration or error in adding the signin button to the view"); + return; + } + } +} diff --git a/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/package-info.java b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/package-info.java new file mode 100644 index 00000000000..69a155da068 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/java/com/amazonaws/mobile/auth/ui/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Package Name. + */ +package com.amazonaws.mobile.auth.ui; diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-hdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-hdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..2d2888be01d Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-hdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-ldpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-ldpi/default_sign_in_logo.png new file mode 100644 index 00000000000..a509bdcffc9 Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-ldpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-mdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-mdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..5cb626ed53c Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-mdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-tvdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-tvdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..5af6a1100bf Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-tvdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-xhdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-xhdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..2e2ebfb99b6 Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-xhdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-xxhdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-xxhdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..2f31d683c74 Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-xxhdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/drawable-xxxhdpi/default_sign_in_logo.png b/aws-android-sdk-auth-ui/src/main/res/drawable-xxxhdpi/default_sign_in_logo.png new file mode 100644 index 00000000000..ff5b8313ced Binary files /dev/null and b/aws-android-sdk-auth-ui/src/main/res/drawable-xxxhdpi/default_sign_in_logo.png differ diff --git a/aws-android-sdk-auth-ui/src/main/res/layout/activity_sign_in.xml b/aws-android-sdk-auth-ui/src/main/res/layout/activity_sign_in.xml new file mode 100644 index 00000000000..ca1bab0798e --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/layout/activity_sign_in.xml @@ -0,0 +1,9 @@ + + diff --git a/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_or_sign_in_divider.xml b/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_or_sign_in_divider.xml new file mode 100644 index 00000000000..f731eccf613 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_or_sign_in_divider.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_sign_in_divider.xml b/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_sign_in_divider.xml new file mode 100644 index 00000000000..1070f2482ef --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/layout/horizontal_sign_in_divider.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/aws-android-sdk-auth-ui/src/main/res/values/attrs.xml b/aws-android-sdk-auth-ui/src/main/res/values/attrs.xml new file mode 100644 index 00000000000..0f09a76318a --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/values/attrs.xml @@ -0,0 +1,22 @@ + + + + + @android:color/darker_gray + + + @drawable/default_sign_in_logo + + + + + + + + + \ No newline at end of file diff --git a/aws-android-sdk-auth-ui/src/main/res/values/colors.xml b/aws-android-sdk-auth-ui/src/main/res/values/colors.xml new file mode 100644 index 00000000000..8fbc637b5fd --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #DDDDDD + diff --git a/aws-android-sdk-auth-ui/src/main/res/values/dimens.xml b/aws-android-sdk-auth-ui/src/main/res/values/dimens.xml new file mode 100644 index 00000000000..3811ba897b9 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/values/dimens.xml @@ -0,0 +1,7 @@ + + + 10dp + 240dp + 40dp + 2dp + diff --git a/aws-android-sdk-auth-ui/src/main/res/values/strings.xml b/aws-android-sdk-auth-ui/src/main/res/values/strings.xml new file mode 100644 index 00000000000..3ccb67746a7 --- /dev/null +++ b/aws-android-sdk-auth-ui/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + + Sign-in + "Sign-in with %s succeeded." + "Sign-in with %s canceled." + or sign in with + Sign in with + diff --git a/aws-android-sdk-auth-userpools/pom.xml b/aws-android-sdk-auth-userpools/pom.xml new file mode 100644 index 00000000000..660f19b27de --- /dev/null +++ b/aws-android-sdk-auth-userpools/pom.xml @@ -0,0 +1,90 @@ + + 4.0.0 + com.amazonaws + aws-android-sdk-auth-userpools + aar + AWS SDK for Android - AWS Cognito Userpools SignIn + The AWS Android SDK for Authentication - Cognito Userpools SignIn holds the client classes that are used for enabling communication with Cognito UserPools SignIn Provider + http://aws.amazon.com/sdkforandroid + + + + UTF-8 + + + UTF-8 + + + + + com.amazonaws + aws-android-sdk-pom + 2.6.0 + + + + + android-support + file://${env.ANDROID_HOME}/extras/android/m2repository/ + + + + + + com.amazonaws + aws-android-sdk-cognitoidentityprovider + false + 2.6.0 + + + + com.amazonaws + aws-android-sdk-auth-core + false + 2.6.0 + aar + + + + com.google.android + android + 4.1.1.4 + provided + + + + com.android.support + support-v4 + 24.2.0 + aar + + + + + + + com.simpligility.maven.plugins + android-maven-plugin + 4.5.0 + true + + + 25 + 19.1.0 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + diff --git a/aws-android-sdk-auth-userpools/src/main/AndroidManifest.xml b/aws-android-sdk-auth-userpools/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..0037533195d --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/CognitoUserPoolsSignInProvider.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/CognitoUserPoolsSignInProvider.java new file mode 100644 index 00000000000..1148f31d7a5 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/CognitoUserPoolsSignInProvider.java @@ -0,0 +1,750 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.VerificationHandler; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoDevice; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserAttributes; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserCodeDeliveryDetails; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserSession; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationContinuation; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationDetails; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ChallengeContinuation; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ForgotPasswordContinuation; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.MultiFactorAuthenticationContinuation; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.AuthenticationHandler; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.ForgotPasswordHandler; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.GenericHandler; +import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.SignUpHandler; + +import com.amazonaws.services.cognitoidentityprovider.model.InvalidParameterException; +import com.amazonaws.services.cognitoidentityprovider.model.NotAuthorizedException; +import com.amazonaws.services.cognitoidentityprovider.model.UserNotConfirmedException; +import com.amazonaws.services.cognitoidentityprovider.model.UserNotFoundException; + +import com.amazonaws.mobile.auth.core.signin.SignInProvider; +import com.amazonaws.mobile.auth.core.signin.SignInProviderResultHandler; +import com.amazonaws.mobile.auth.core.internal.util.ViewHelper; + +import com.amazonaws.regions.Regions; + +import java.util.HashSet; +import java.util.Set; + +/** + * Manages sign-in using Cognito User Pools. + */ +public class CognitoUserPoolsSignInProvider implements SignInProvider { + /** + * Cognito User Pools attributes. + */ + public static final class AttributeKeys { + + /** Username attribute. */ + public static final String USERNAME = "username"; + + /** Password attribute. */ + public static final String PASSWORD = "password"; + + /** Verification code attribute. */ + public static final String VERIFICATION_CODE = "verification_code"; + + /** Given name attribute. */ + public static final String GIVEN_NAME = "given_name"; + + /** Email address attribute. */ + public static final String EMAIL_ADDRESS = "email"; + + /** Phone number attribute. */ + public static final String PHONE_NUMBER = "phone_number"; + + /** Background Color. */ + public static final String BACKGROUND_COLOR = "signInBackgroundColor"; + } + + /** Log tag. */ + private static final String LOG_TAG = CognitoUserPoolsSignInProvider.class.getSimpleName(); + + /** Start of Intent request codes owned by the Cognito User Pools app. */ + private static final int REQUEST_CODE_START = 0x2970; + + /** Request code for password reset Intent. */ + private static final int FORGOT_PASSWORD_REQUEST_CODE = REQUEST_CODE_START + 42; + + /** Request code for account registration Intent. */ + private static final int SIGN_UP_REQUEST_CODE = REQUEST_CODE_START + 43; + + /** Request code for MFA Intent. */ + private static final int MFA_REQUEST_CODE = REQUEST_CODE_START + 44; + + /** Request code for account verification Intent. */ + private static final int VERIFICATION_REQUEST_CODE = REQUEST_CODE_START + 45; + + /** Request codes that the Cognito User Pools can handle. */ + private static final Set REQUEST_CODES = new HashSet() { { + add(FORGOT_PASSWORD_REQUEST_CODE); + add(SIGN_UP_REQUEST_CODE); + add(MFA_REQUEST_CODE); + add(VERIFICATION_REQUEST_CODE); + } }; + + /** Stores the configuration file name. */ + private static final String AWS_CONFIGURATION_FILE = "AWSConfiguration"; + + /** Minimum length of password supported by Cognito. */ + private static final int PASSWORD_MIN_LENGTH = 6; + + /** Prefix of the exception message. */ + private static final String USERPOOLS_EXCEPTION_PREFIX = "(Service"; + + /** The sign-in results adapter from the SignInManager. */ + private SignInProviderResultHandler resultsHandler; + + /** Forgot Password processing provided by the Cognito User Pools SDK. */ + private ForgotPasswordContinuation forgotPasswordContinuation; + + /** MFA processing provided by the Cognito User Pools SDK. */ + private MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation; + + /** Android context. */ + private Context context; + + /** Invoking Android Activity. */ + private Activity activity; + + /** Sign-in username. */ + private String username; + + /** Sign-in password. */ + private String password; + + /** Sign-in verification code. */ + private String verificationCode; + + /** The key for CognitoUserPools Login. */ + private String cognitoLoginKey; + + /** Active Cognito User Pool. */ + private CognitoUserPool cognitoUserPool; + + /** Active Cognito User Pools session. */ + private CognitoUserSession cognitoUserSession; + + /** AWSConfiguration object. */ + private AWSConfiguration awsConfiguration; + + /** Background Color for the View. */ + private int backgroundColor; + + /** + * Handle callbacks from the Forgot Password flow. + */ + private ForgotPasswordHandler forgotPasswordHandler = new ForgotPasswordHandler() { + @Override + public void onSuccess() { + Log.d(LOG_TAG, "Password change succeeded."); + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_forgot_password), + activity.getString(R.string.password_change_success)); + } + + @Override + public void getResetCode(final ForgotPasswordContinuation continuation) { + forgotPasswordContinuation = continuation; + + final Intent intent = new Intent(context, ForgotPasswordActivity.class); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + CognitoUserPoolsSignInProvider.this.backgroundColor); + activity.startActivityForResult(intent, FORGOT_PASSWORD_REQUEST_CODE); + } + + @Override + public void onFailure(final Exception exception) { + Log.e(LOG_TAG, "Password change failed.", exception); + + final String message; + if (exception instanceof InvalidParameterException) { + message = activity.getString(R.string.password_change_no_verification_failed); + } else { + message = getErrorMessageFromException(exception); + } + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_forgot_password), + activity.getString(R.string.password_change_failed) + " " + message); + } + }; + + /** + * Start the SignUp Confirm Activity with the attribte keys. + */ + private void startVerificationActivity() { + final Intent intent = new Intent(context, SignUpConfirmActivity.class); + intent.putExtra(AttributeKeys.USERNAME, username); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + CognitoUserPoolsSignInProvider.this.backgroundColor); + activity.startActivityForResult(intent, VERIFICATION_REQUEST_CODE); + } + + /** + * Handle callbacks from the Sign Up flow. + */ + private SignUpHandler signUpHandler = new SignUpHandler() { + @Override + public void onSuccess(final CognitoUser user, final boolean signUpConfirmationState, + final CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) { + if (signUpConfirmationState) { + Log.d(LOG_TAG, "Signed up. User ID = " + user.getUserId()); + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up), + activity.getString(R.string.sign_up_success) + " " + user.getUserId()); + } else { + Log.w(LOG_TAG, "Additional confirmation for sign up."); + + startVerificationActivity(); + } + } + + @Override + public void onFailure(final Exception exception) { + Log.e(LOG_TAG, "Sign up failed.", exception); + ViewHelper.showDialog(activity, activity.getString(R.string.title_dialog_sign_up_failed), + exception.getLocalizedMessage() != null ? getErrorMessageFromException(exception) + : activity.getString(R.string.sign_up_failed)); + } + }; + + /** + * Handle callbacks from the Sign Up - Confirm Account flow. + */ + private GenericHandler signUpConfirmationHandler = new GenericHandler() { + @Override + public void onSuccess() { + Log.i(LOG_TAG, "Confirmed."); + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up_confirm), + activity.getString(R.string.sign_up_confirm_success)); + } + + @Override + public void onFailure(final Exception exception) { + Log.e(LOG_TAG, "Failed to confirm user.", exception); + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up_confirm), + activity.getString(R.string.sign_up_confirm_failed) + " " + getErrorMessageFromException(exception)); + } + }; + + /** + * Resent the confirmation code on MFA. + */ + private void resendConfirmationCode() { + final CognitoUser cognitoUser = cognitoUserPool.getUser(username); + cognitoUser.resendConfirmationCodeInBackground(new VerificationHandler() { + @Override + public void onSuccess(final CognitoUserCodeDeliveryDetails verificationCodeDeliveryMedium) { + startVerificationActivity(); + } + + @Override + public void onFailure(final Exception exception) { + if (null != resultsHandler) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_in), + activity.getString(R.string.login_failed) + + "\nUser was not verified and resending confirmation code failed.\n" + + getErrorMessageFromException(exception)); + + resultsHandler.onError(CognitoUserPoolsSignInProvider.this, exception); + } + } + }); + } + + /** + * Handle callbacks from the Authentication flow. Includes MFA handling. + */ + private AuthenticationHandler authenticationHandler = new AuthenticationHandler() { + @Override + public void onSuccess(final CognitoUserSession userSession, final CognitoDevice newDevice) { + Log.i(LOG_TAG, "Logged in. " + userSession.getIdToken()); + + cognitoUserSession = userSession; + + if (null != resultsHandler) { + resultsHandler.onSuccess(CognitoUserPoolsSignInProvider.this); + } + } + + @Override + public void getAuthenticationDetails( + final AuthenticationContinuation authenticationContinuation, final String userId) { + + if (null != username && null != password) { + final AuthenticationDetails authenticationDetails = new AuthenticationDetails( + username, + password, + null); + + authenticationContinuation.setAuthenticationDetails(authenticationDetails); + authenticationContinuation.continueTask(); + } + } + + @Override + public void getMFACode(final MultiFactorAuthenticationContinuation continuation) { + multiFactorAuthenticationContinuation = continuation; + + final Intent intent = new Intent(context, MFAActivity.class); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + CognitoUserPoolsSignInProvider.this.backgroundColor); + activity.startActivityForResult(intent, MFA_REQUEST_CODE); + } + + @Override + public void authenticationChallenge(final ChallengeContinuation continuation) { + throw new UnsupportedOperationException("Not supported in this sample."); + } + + @Override + public void onFailure(final Exception exception) { + Log.e(LOG_TAG, "Failed to login.", exception); + + final String message; + + // UserNotConfirmedException will only happen once in the sign-in flow in the case + // that the user attempting to sign in had not confirmed their account by entering + // the correct verification code. A different exception is thrown if the code + // is invalid, so this will not create an continuous confirmation loop if the + // user enters the wrong code. + if (exception instanceof UserNotConfirmedException) { + resendConfirmationCode(); + return; + } + + if (exception instanceof UserNotFoundException) { + message = activity.getString(R.string.user_does_not_exist); + } else if (exception instanceof NotAuthorizedException) { + message = activity.getString(R.string.incorrect_username_or_password); + } else { + message = getErrorMessageFromException(exception); + } + + + if (null != resultsHandler) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_in), + activity.getString(R.string.login_failed) + " " + message); + resultsHandler.onError(CognitoUserPoolsSignInProvider.this, exception); + } + } + }; + + /** {@inheritDoc} */ + @Override + public void initialize(final Context context, final AWSConfiguration awsConfiguration) { + this.context = context; + this.awsConfiguration = awsConfiguration; + + Log.d(LOG_TAG, "initalizing Cognito User Pools"); + + final String regionString = getCognitoUserPoolRegion(); + final Regions region = Regions.fromName(regionString); + + this.cognitoUserPool = new CognitoUserPool(context, + getCognitoUserPoolId(), + getCognitoUserPoolClientId(), + getCognitoUserPoolClientSecret(), + region); + + cognitoLoginKey = "cognito-idp." + region.getName() + + ".amazonaws.com/" + getCognitoUserPoolId(); + Log.d(LOG_TAG, "CognitoLoginKey: " + cognitoLoginKey); + } + + /** {@inheritDoc} */ + @Override + public boolean isRequestCodeOurs(final int requestCode) { + return REQUEST_CODES.contains(requestCode); + } + + /** {@inheritDoc} */ + @Override + public void handleActivityResult(final int requestCode, + final int resultCode, + final Intent data) { + + if (Activity.RESULT_OK == resultCode) { + switch (requestCode) { + case FORGOT_PASSWORD_REQUEST_CODE: + password = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PASSWORD); + verificationCode = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE); + + if (password.length() < PASSWORD_MIN_LENGTH) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_forgot_password), + activity.getString(R.string.password_change_failed) + + " " + activity.getString(R.string.password_length_validation_failed)); + return; + } + + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + forgotPasswordContinuation.setPassword(password); + forgotPasswordContinuation.setVerificationCode(verificationCode); + forgotPasswordContinuation.continueTask(); + break; + case SIGN_UP_REQUEST_CODE: + username = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.USERNAME); + password = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PASSWORD); + final String givenName = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.GIVEN_NAME); + final String email = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.EMAIL_ADDRESS); + final String phone = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PHONE_NUMBER); + + if (username.length() < 1) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up), + activity.getString(R.string.sign_up_failed) + + " " + activity.getString(R.string.sign_up_username_missing)); + return; + } + + if (password.length() < PASSWORD_MIN_LENGTH) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up), + activity.getString(R.string.sign_up_failed) + + " " + activity.getString(R.string.password_length_validation_failed)); + return; + } + + Log.d(LOG_TAG, "username = " + username); + Log.d(LOG_TAG, "given_name = " + givenName); + Log.d(LOG_TAG, "email = " + email); + Log.d(LOG_TAG, "phone = " + phone); + + final CognitoUserAttributes userAttributes = new CognitoUserAttributes(); + userAttributes.addAttribute(CognitoUserPoolsSignInProvider.AttributeKeys.GIVEN_NAME, givenName); + userAttributes.addAttribute(CognitoUserPoolsSignInProvider.AttributeKeys.EMAIL_ADDRESS, email); + + if (null != phone && phone.length() > 0) { + userAttributes.addAttribute(CognitoUserPoolsSignInProvider.AttributeKeys.PHONE_NUMBER, phone); + } + + cognitoUserPool.signUpInBackground(username, password, userAttributes, + null, signUpHandler); + + break; + case MFA_REQUEST_CODE: + verificationCode = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE); + + if (verificationCode.length() < 1) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_mfa), + activity.getString(R.string.mfa_failed) + + " " + activity.getString(R.string.mfa_code_empty)); + return; + } + + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + multiFactorAuthenticationContinuation.setMfaCode(verificationCode); + multiFactorAuthenticationContinuation.continueTask(); + + break; + case VERIFICATION_REQUEST_CODE: + username = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.USERNAME); + verificationCode = data.getStringExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE); + + if (username.length() < 1) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up_confirm), + activity.getString(R.string.sign_up_confirm_title) + + " " + activity.getString(R.string.sign_up_username_missing)); + return; + } + + if (verificationCode.length() < 1) { + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_up_confirm), + activity.getString(R.string.sign_up_confirm_title) + + " " + activity.getString(R.string.sign_up_confirm_code_missing)); + return; + } + + Log.d(LOG_TAG, "username = " + username); + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + final CognitoUser cognitoUser = cognitoUserPool.getUser(username); + + cognitoUser.confirmSignUpInBackground(verificationCode, true, signUpConfirmationHandler); + + break; + default: + Log.e(LOG_TAG, "Unknown Request Code sent."); + } + } + + } + + /** {@inheritDoc} */ + @Override + public View.OnClickListener initializeSignInButton(final Activity signInActivity, + final View buttonView, + final SignInProviderResultHandler providerResultsHandler) { + this.activity = signInActivity; + this.resultsHandler = providerResultsHandler; + + final UserPoolSignInView userPoolSignInView = + (UserPoolSignInView) activity.findViewById(R.id.user_pool_sign_in_view_id); + + this.backgroundColor = userPoolSignInView.getBackgroundColor(); + + userPoolSignInView.getSignUpTextView().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(context, SignUpActivity.class); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + CognitoUserPoolsSignInProvider.this.backgroundColor); + activity.startActivityForResult(intent, CognitoUserPoolsSignInProvider.SIGN_UP_REQUEST_CODE); + } + }); + + final TextView forgotPasswordTextView = userPoolSignInView.getForgotPasswordTextView(); + forgotPasswordTextView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(final View view) { + username = userPoolSignInView.getEnteredUserName(); + if (username.length() < 1) { + Log.w(LOG_TAG, "Missing username."); + ViewHelper.showDialog(activity, activity.getString(R.string.title_activity_sign_in), "Missing username."); + } else { + final CognitoUser cognitoUser = cognitoUserPool.getUser(username); + + cognitoUser.forgotPasswordInBackground(forgotPasswordHandler); + } + } + }); + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View view) { + username = userPoolSignInView.getEnteredUserName(); + password = userPoolSignInView.getEnteredPassword(); + + final CognitoUser cognitoUser = cognitoUserPool.getUser(username); + + cognitoUser.getSessionInBackground(authenticationHandler); + } + }; + + buttonView.setOnClickListener(listener); + return listener; + } + + /** {@inheritDoc} */ + @Override + public String getDisplayName() { + return "Amazon Cognito Your User Pools"; + } + + /** {@inheritDoc} */ + @Override + public String getCognitoLoginKey() { + return cognitoLoginKey; + } + + /** + * Authentication handler for handling token refresh. + */ + private static class RefreshSessionAuthenticationHandler implements AuthenticationHandler { + private CognitoUserSession userSession = null; + + private CognitoUserSession getUserSession() { + return userSession; + } + + @Override + public void onSuccess(final CognitoUserSession userSession, final CognitoDevice newDevice) { + this.userSession = userSession; + } + + @Override + public void getAuthenticationDetails(final AuthenticationContinuation authenticationContinuation, + final String UserId) { + Log.d(LOG_TAG, "Can't refresh the session silently, due to authentication details needed."); + } + + @Override + public void getMFACode(final MultiFactorAuthenticationContinuation continuation) { + Log.wtf(LOG_TAG, "Refresh flow can not trigger request for MFA code."); + } + + @Override + public void authenticationChallenge(final ChallengeContinuation continuation) { + Log.wtf(LOG_TAG, "Refresh flow can not trigger request for authentication challenge."); + } + + @Override + public void onFailure(final Exception exception) { + Log.e(LOG_TAG, "Can't refresh session.", exception); + } + } + + /** {@inheritDoc} */ + @Override + public boolean refreshUserSignInState() { + if (null != cognitoUserSession && cognitoUserSession.isValid()) { + return true; + } + + final RefreshSessionAuthenticationHandler refreshSessionAuthenticationHandler + = new RefreshSessionAuthenticationHandler(); + + cognitoUserPool.getCurrentUser().getSession(refreshSessionAuthenticationHandler); + if (null != refreshSessionAuthenticationHandler.getUserSession()) { + cognitoUserSession = refreshSessionAuthenticationHandler.getUserSession(); + Log.i(LOG_TAG, "refreshUserSignInState: Signed in with Cognito."); + return true; + } + + Log.i(LOG_TAG, "refreshUserSignInState: Not signed in with Cognito."); + cognitoUserSession = null; + return false; + } + + /** {@inheritDoc} */ + @Override + public String getToken() { + return null == cognitoUserSession ? null : cognitoUserSession.getIdToken().getJWTToken(); + } + + /** {@inheritDoc} */ + @Override + public String refreshToken() { + // If there is a session, but the credentials are expired rendering the session not valid. + if ((cognitoUserSession != null) && !cognitoUserSession.isValid()) { + // Attempt to refresh the credentials. + final RefreshSessionAuthenticationHandler refreshSessionAuthenticationHandler + = new RefreshSessionAuthenticationHandler(); + + // Cognito User Pools SDK will attempt to refresh the token upon calling getSession(). + cognitoUserPool.getCurrentUser().getSession(refreshSessionAuthenticationHandler); + + if (null != refreshSessionAuthenticationHandler.getUserSession()) { + cognitoUserSession = refreshSessionAuthenticationHandler.getUserSession(); + } else { + Log.e(LOG_TAG, "Could not refresh the Cognito User Pool Token."); + } + } + + return getToken(); + } + + /** {@inheritDoc} */ + @Override + public void signOut() { + if (null != cognitoUserPool && null != cognitoUserPool.getCurrentUser()) { + cognitoUserPool.getCurrentUser().signOut(); + + cognitoUserSession = null; + username = null; + password = null; + } + } + + /** + * @return the Cognito User Pool. + */ + public CognitoUserPool getCognitoUserPool() { + return cognitoUserPool; + } + + /** + * Retrieve the Cognito UserPool Id from CognitoUserPool -> PoolId key + * + * @throws IllegalArgumentException + * @return CognitoUserPoolId + */ + private String getCognitoUserPoolId() throws IllegalArgumentException { + try { + return this.awsConfiguration.optJsonObject("CognitoUserPool").getString("PoolId"); + } catch (Exception exception) { + throw new IllegalArgumentException( + "Cannot find the PoolId from the " + AWS_CONFIGURATION_FILE + " file.", exception); + } + } + + /** + * Retrieve the Cognito UserPool Id from CognitoUserPool -> AppClientId key + * + * @return CognitoUserPoolId + * @throws IllegalArgumentException + */ + private String getCognitoUserPoolClientId() throws IllegalArgumentException { + try { + return this.awsConfiguration.optJsonObject("CognitoUserPool").getString("AppClientId"); + } catch (Exception exception) { + throw new IllegalArgumentException( + "Cannot find the CognitoUserPool AppClientId from the " + AWS_CONFIGURATION_FILE + " file.", exception); + } + } + + /** + * Retrieve the Cognito UserPool ClientSecret from CognitoUserPool -> AppClientSecret key + * + * @return CognitoUserPoolAppClientSecret + * @throws IllegalArgumentException + */ + private String getCognitoUserPoolClientSecret() throws IllegalArgumentException { + try { + return this.awsConfiguration.optJsonObject("CognitoUserPool").getString("AppClientSecret"); + } catch (Exception exception) { + throw new IllegalArgumentException( + "Cannot find the CognitoUserPool AppClientSecret from the " + AWS_CONFIGURATION_FILE + " file.", exception); + } + } + + /** + * Retrieve the Cognito Region from CognitoUserPool -> Region key + * + * @return CognitoUserPool Region + * @throws IllegalArgumentException + */ + private String getCognitoUserPoolRegion() throws IllegalArgumentException { + try { + return this.awsConfiguration.optJsonObject("CognitoUserPool").getString("Region"); + } catch (Exception exception) { + throw new IllegalArgumentException("Cannot find the CognitoUserPool Region from the " + + AWS_CONFIGURATION_FILE + " file.", exception); + } + } + + /** + * Extract the error message from the exception object. + * @param exception The exception object thrown by Cognito IdentityProvider. + * */ + private static String getErrorMessageFromException(final Exception exception) { + final String message = exception.getLocalizedMessage(); + if (message == null) { + return exception.getMessage(); + } + final int index = message.indexOf(USERPOOLS_EXCEPTION_PREFIX); + if (index == -1) { + return message; + } else { + return message.substring(0, index); + } + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordActivity.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordActivity.java new file mode 100644 index 00000000000..c5fe692dd6a --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordActivity.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +/** + * Activity to prompt for a new password along with the verification code. + */ +public class ForgotPasswordActivity extends Activity { + /** Log tag. */ + private static final String LOG_TAG = ForgotPasswordActivity.class.getSimpleName(); + + private ForgotPasswordView forgotPasswordView; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_forgot_password); + forgotPasswordView = (ForgotPasswordView) findViewById(R.id.forgot_password_view); + } + + /** + * Retrieve input and return to caller. + * @param view the Android View + */ + public void forgotPassword(final View view) { + final String password = forgotPasswordView.getPassword(); + final String verificationCode = forgotPasswordView.getVerificationCode(); + + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + final Intent intent = new Intent(); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PASSWORD, password); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE, verificationCode); + + setResult(RESULT_OK, intent); + + finish(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordView.java new file mode 100644 index 00000000000..a10503f96d6 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/ForgotPasswordView.java @@ -0,0 +1,159 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils; +import com.amazonaws.mobile.auth.core.signin.ui.SplitBackgroundDrawable; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_COLOR; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_CORNER_RADIUS; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_SIDE_MARGIN_RATIO; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.MAX_FORM_WIDTH_IN_PIXELS; + +/** + * This view present the ForgotPassword screen for the user to reset the + * password. + */ +public class ForgotPasswordView extends LinearLayout { + private FormView forgotPassForm; + private EditText verificationCodeEditText; + private EditText passwordEditText; + + private SplitBackgroundDrawable splitBackgroundDrawable; + + /** + * Constructs the ForgotPassword View. + * @param context The activity context. + */ + public ForgotPasswordView(final Context context) { + this(context, null); + } + + /** + * Constructs the ForgotPassword View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + */ + public ForgotPasswordView(final Context context, @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructs the ForgotPassword View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + * @param defStyleAttr The resource identifier for the default style attribute. + */ + public ForgotPasswordView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOrientation(VERTICAL); + + final int backgroundColor; + if (isInEditMode()) { + backgroundColor = Color.DKGRAY; + } else { + final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.ForgotPasswordView); + backgroundColor = styledAttributes.getInt(R.styleable.ForgotPasswordView_forgotPasswordViewBackgroundColor, + Color.DKGRAY); + styledAttributes.recycle(); + } + + splitBackgroundDrawable = new SplitBackgroundDrawable(0, getBackgroundColor(context, backgroundColor)); + } + + private int getBackgroundColor(final Context context, final int defaultBackgroundColor) { + Intent intent = ((Activity) context).getIntent(); + return (int) (intent.getIntExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + defaultBackgroundColor)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + forgotPassForm = (FormView) findViewById(R.id.forgot_password_form); + + verificationCodeEditText = forgotPassForm.addFormField(getContext(), + InputType.TYPE_CLASS_NUMBER, + getContext().getString(R.string.sign_up_confirm_code)); + + passwordEditText = forgotPassForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD, + getContext().getString(R.string.sign_in_password)); + + setupConfirmButtonColor(); + } + + private void setupConfirmButtonColor() { + final Button confirmButton = (Button) findViewById(R.id.forgot_password_button); + confirmButton.setBackgroundDrawable(DisplayUtils.getRoundedRectangleBackground( + FORM_BUTTON_CORNER_RADIUS, FORM_BUTTON_COLOR)); + LayoutParams signUpButtonLayoutParams = (LayoutParams) confirmButton.getLayoutParams(); + signUpButtonLayoutParams.setMargins( + forgotPassForm.getFormShadowMargin(), + signUpButtonLayoutParams.topMargin, + forgotPassForm.getFormShadowMargin(), + signUpButtonLayoutParams.bottomMargin); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = Math.min((int)(parentWidth * FORM_SIDE_MARGIN_RATIO), MAX_FORM_WIDTH_IN_PIXELS); + super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setupSplitBackground(); + } + + private void setupSplitBackground() { + splitBackgroundDrawable.setSplitPointDistanceFromTop(forgotPassForm.getTop() + + (forgotPassForm.getMeasuredHeight()/2)); + ((ViewGroup) getParent()).setBackgroundDrawable(splitBackgroundDrawable); + } + + public String getVerificationCode() { + return verificationCodeEditText.getText().toString(); + } + + public String getPassword() { + return passwordEditText.getText().toString(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormEditText.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormEditText.java new file mode 100644 index 00000000000..f3c2b138e46 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormEditText.java @@ -0,0 +1,184 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.content.Context; +import android.graphics.Color; +import android.support.annotation.IdRes; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; + +import java.util.Locale; + +/** + * An EditText that shows the hint as a floating label once text is entered in the view. + */ +public class FormEditText extends LinearLayout { + private static final int TEXT_VIEW_ID = 0xF01; + private static final int EDIT_TEXT_ID = 0xF02; + private static final int BIT_FOR_SHOWING_PASSWORD = InputType.TYPE_TEXT_VARIATION_PASSWORD ^ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + private static final int TEXT_VIEW_TOP_MARGIN = dp(5); + private static final int EDIT_VIEW_BOTTOM_PADDING = dp(5); + private static final int SHOW_PASSWORD_LEFT_RIGHT_MARGIN = dp(5); + private static final int SHOW_PASSWORD_TOP_MARGIN = dp(-5); // float the show box up. + private TextView textView; + private EditText editText; + private LinearLayout editFieldLayout; + private TextView showPasswordToggleTextView = null; + private boolean hasSetMinimumSize = false; + + @IdRes + private int toViewId(int id) { + return id; + } + + public FormEditText(Context context, int inputType, final String fieldName) { + super(context); + this.setOrientation(VERTICAL); + this.setGravity(Gravity.CENTER_VERTICAL); + + textView = new TextView(context); + textView.setText(fieldName.toUpperCase(Locale.getDefault())); + textView.setId(toViewId(TEXT_VIEW_ID)); + final LinearLayout.LayoutParams textViewLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + textViewLayoutParams.setMargins(0, TEXT_VIEW_TOP_MARGIN, 0, 0); + this.addView(textView, textViewLayoutParams); + textView.setVisibility(INVISIBLE); + + editText = new EditText(context); + editText.setSingleLine(); + editText.setInputType(inputType); + editText.setBackgroundColor(Color.TRANSPARENT); + editText.setPadding(0,dp(2), 0, dp(2) + EDIT_VIEW_BOTTOM_PADDING); + editText.setId(toViewId(EDIT_TEXT_ID)); + editText.setHint(fieldName); + + final LinearLayout.LayoutParams editTextLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + editTextLayoutParams.setMargins(0, 0, 0, 0); + + if ((inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD) > 0) { + editFieldLayout = new LinearLayout(context); + editFieldLayout.setOrientation(HORIZONTAL); + editTextLayoutParams.gravity = Gravity.START; + editTextLayoutParams.weight = 1; + editFieldLayout.addView(editText, editTextLayoutParams); + + showPasswordToggleTextView = new TextView(context); + setupShowHidePassword(); + this.addView(editFieldLayout); + + } else { + this.addView(editText, editTextLayoutParams); + } + + setupTextChangedListener(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (!hasSetMinimumSize) { + this.setMinimumHeight(textView.getMeasuredHeight() + TEXT_VIEW_TOP_MARGIN + editText.getMeasuredHeight()); + textView.setVisibility(GONE); + hasSetMinimumSize = true; + } + } + + private void setupShowHidePassword() { + final String showText = getResources().getString(R.string.sign_in_show_password); + final String hideText = getResources().getString(R.string.sign_in_hide_password); + + showPasswordToggleTextView.setText(showText); + final LinearLayout.LayoutParams showPassLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + showPassLayoutParams.setMargins( + SHOW_PASSWORD_LEFT_RIGHT_MARGIN, SHOW_PASSWORD_TOP_MARGIN, SHOW_PASSWORD_LEFT_RIGHT_MARGIN, 0); + showPassLayoutParams.gravity = Gravity.END | Gravity.CENTER_VERTICAL; + + editFieldLayout.addView(showPasswordToggleTextView, showPassLayoutParams); + showPasswordToggleTextView.setVisibility(GONE); + + showPasswordToggleTextView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final CharSequence oldText = showPasswordToggleTextView.getText(); + final CharSequence newText = oldText.equals(showText) ? hideText : showText; + showPasswordToggleTextView.setText(newText); + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + if (oldText.equals(showText)) { + editText.setInputType(editText.getInputType() | BIT_FOR_SHOWING_PASSWORD); + } else { + editText.setInputType(editText.getInputType() & ~BIT_FOR_SHOWING_PASSWORD); + } + editText.setSelection(selectionStart, selectionEnd); + } + }); + } + + private void setupTextChangedListener() { + editText.addTextChangedListener(new TextWatcher() { + private void handleFloatingTextVisibility() { + if (editText.getText().length() == 0) { + if (hasSetMinimumSize) { + textView.setVisibility(GONE); + } + editText.setPadding(0,dp(2), 0, dp(2) + EDIT_VIEW_BOTTOM_PADDING); + if (showPasswordToggleTextView != null) { + showPasswordToggleTextView.setVisibility(GONE); + } + } else { + textView.setVisibility(VISIBLE); + editText.setPadding(0,dp(1), 0, dp(3) + EDIT_VIEW_BOTTOM_PADDING); + if (showPasswordToggleTextView != null) { + showPasswordToggleTextView.setVisibility(VISIBLE); + } + } + } + + @Override + public void beforeTextChanged(final CharSequence text, final int start, final int count, final int after) { + } + + @Override + public void onTextChanged(final CharSequence text, final int start, final int before, final int count) { + } + + @Override + public void afterTextChanged(final Editable text) { + handleFloatingTextVisibility(); + } + }); + } + + public EditText getEditTextView() { + return editText; + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormView.java new file mode 100644 index 00000000000..85a3bf4b612 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/FormView.java @@ -0,0 +1,159 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.getRoundedRectangleBackground; + +/** + * A view for displaying text and passwords. + */ +public class FormView extends LinearLayout { + /** Corner radius. */ + private static final int FORM_CORNER_RADIUS = dp(8); + private static final int FIELD_LEFT_RIGHT_MARGIN = dp(20); + + /** Background Drawables for the form. */ + private final Drawable[] backgroundDrawables = new Drawable[] { + // Border Shadow + createRoundedRectShape(FORM_CORNER_RADIUS, Color.DKGRAY, 10), + createRoundedRectShape(FORM_CORNER_RADIUS, Color.DKGRAY, 20), + createRoundedRectShape(FORM_CORNER_RADIUS, Color.DKGRAY, 30), + createRoundedRectShape(FORM_CORNER_RADIUS, Color.DKGRAY, 50), + createRoundedRectShape(FORM_CORNER_RADIUS, Color.DKGRAY, 80), + // Background Color + createRoundedRectShape(FORM_CORNER_RADIUS, Color.WHITE, 100)}; + + public FormView(final Context context) { + this(context, null); + } + + public FormView(final Context context, @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + public FormView(final Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.setOrientation(VERTICAL); + this.setBackgroundDrawable(getFormBackground()); + } + + private ShapeDrawable createRoundedRectShape(final int cornerRadius, final int color, final int alpha) { + final ShapeDrawable insetBorderDrawable = + getRoundedRectangleBackground(cornerRadius, color); + if (alpha < 100) { + insetBorderDrawable.setAlpha(alpha); + } + insetBorderDrawable.getPaint().setColor(color); + return insetBorderDrawable; + } + + /** + * Create the form background. + * @return the background drawable. + */ + private Drawable getFormBackground() { + final LayerDrawable layerDrawable = new LayerDrawable(backgroundDrawables); + + for (int i = 0; i < backgroundDrawables.length; i++) { + layerDrawable.setLayerInset(i, dp(i), dp(i), dp(i), dp(i)); + } + + return layerDrawable; + } + + private static class Divider extends View { + final Paint paint; + public Divider(Context context) { + super(context); + paint = new Paint(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + paint.setColor(Color.LTGRAY); + canvas.drawRect(getMeasuredWidth() * 0.1f, 0, getMeasuredWidth() * 0.9f, getMeasuredHeight(), paint); + } + } + + private EditText addField(final Context context, int inputType, final String fieldName) { + final int additionalTopMarginForFirstElement; + + if (getChildCount() == 0) { + additionalTopMarginForFirstElement = getFormShadowMargin(); + } else { + additionalTopMarginForFirstElement = 0; + + // Get previous ites layout params. + final LinearLayout.LayoutParams prevFormEditTextLayoutParams + = (LayoutParams)getChildAt(getChildCount() - 1).getLayoutParams(); + + // Clear previous item's bottom margin. + prevFormEditTextLayoutParams.setMargins( + prevFormEditTextLayoutParams.leftMargin, + prevFormEditTextLayoutParams.topMargin, + prevFormEditTextLayoutParams.rightMargin, + 0); + + // Add a divider before the next item. + addView(new Divider(context), LayoutParams.MATCH_PARENT, dp(1)); + } + + final FormEditText formEditText = new FormEditText(context, inputType, fieldName); + final LinearLayout.LayoutParams formEditTextLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + + formEditTextLayoutParams.setMargins(FIELD_LEFT_RIGHT_MARGIN, additionalTopMarginForFirstElement, FIELD_LEFT_RIGHT_MARGIN, getFormShadowMargin()); + this.addView(formEditText, formEditTextLayoutParams); + + return formEditText.getEditTextView(); + } + + + /** + * Add a field to the form. + * @param context the context. + * @param inputType the desired EditText input type. + * @param fieldName the field name. + * @return the EditText object created within the form. + */ + public EditText addFormField(final Context context, int inputType, final String fieldName) { + return addField(context, inputType, fieldName); + } + + /** + * @return the number of margin pixels drawn on each side of the form. + */ + public int getFormShadowMargin() { + return dp(backgroundDrawables.length - 1); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAActivity.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAActivity.java new file mode 100644 index 00000000000..a32fa703637 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAActivity.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import com.amazonaws.mobile.auth.userpools.R; + +/** + * Activity to prompt for a a verification code. + */ +public class MFAActivity extends Activity { + /** Log tag. */ + private static final String LOG_TAG = MFAActivity.class.getSimpleName(); + private MFAView mfaView; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_mfa); + mfaView = (MFAView) findViewById(R.id.mfa_view); + } + + /** + * Retrieve input and return to caller. + * @param view the Android View + */ + public void verify(final View view) { + final String verificationCode = mfaView.getMFACode(); + + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + final Intent intent = new Intent(); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE, verificationCode); + + setResult(RESULT_OK, intent); + + finish(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAView.java new file mode 100644 index 00000000000..94099cc6e6c --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/MFAView.java @@ -0,0 +1,145 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils; +import com.amazonaws.mobile.auth.core.signin.ui.SplitBackgroundDrawable; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_COLOR; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_CORNER_RADIUS; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_SIDE_MARGIN_RATIO; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.MAX_FORM_WIDTH_IN_PIXELS; + +/** + * View for showing MFA confirmation upon sign-in. + */ +public class MFAView extends LinearLayout { + private FormView mfaForm; + private EditText mfaCodeEditText; + private SplitBackgroundDrawable splitBackgroundDrawable; + + /** + * Constructs the MFA View. + * @param context The activity context. + */ + public MFAView(Context context) { + this(context, null); + } + + /** + * Constructs the MFA View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + */ + public MFAView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructs the MFA View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + * @param defStyleAttr The resource identifier for the default style attribute. + */ + public MFAView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOrientation(VERTICAL); + final int backgroundColor; + if (isInEditMode()) { + backgroundColor = Color.DKGRAY; + } else { + final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.MFAView); + backgroundColor = styledAttributes.getInt(R.styleable.MFAView_mfaViewBackgroundColor, Color.DKGRAY); + styledAttributes.recycle(); + } + + splitBackgroundDrawable = new SplitBackgroundDrawable(0, getBackgroundColor(context, backgroundColor)); + } + + private int getBackgroundColor(final Context context, final int defaultBackgroundColor) { + Intent intent = ((Activity) context).getIntent(); + return (int) (intent.getIntExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + defaultBackgroundColor)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mfaForm = (FormView) findViewById(R.id.mfa_form); + + mfaCodeEditText = mfaForm.addFormField(getContext(), + InputType.TYPE_CLASS_NUMBER, + getContext().getString(R.string.forgot_password_input_code_hint)); + + setupVerifyButtonColor(); + } + + private void setupVerifyButtonColor() { + final Button confirmButton = (Button) findViewById(R.id.mfa_button); + confirmButton.setBackgroundDrawable( + DisplayUtils.getRoundedRectangleBackground(FORM_BUTTON_CORNER_RADIUS, FORM_BUTTON_COLOR)); + final LayoutParams signUpButtonLayoutParams = (LayoutParams) confirmButton.getLayoutParams(); + signUpButtonLayoutParams.setMargins( + mfaForm.getFormShadowMargin(), + signUpButtonLayoutParams.topMargin, + mfaForm.getFormShadowMargin(), + signUpButtonLayoutParams.bottomMargin); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = Math.min((int)(parentWidth * FORM_SIDE_MARGIN_RATIO), MAX_FORM_WIDTH_IN_PIXELS); + super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setupSplitBackground(); + } + + private void setupSplitBackground() { + splitBackgroundDrawable.setSplitPointDistanceFromTop(mfaForm.getTop() + + (mfaForm.getMeasuredHeight()/2)); + ((ViewGroup)getParent()).setBackgroundDrawable(splitBackgroundDrawable); + } + + public String getMFACode() { + return mfaCodeEditText.getText().toString(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpActivity.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpActivity.java new file mode 100644 index 00000000000..48e4451c6c9 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpActivity.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import com.amazonaws.mobile.auth.userpools.R; + +/** + * Activity to prompt for account sign up information. + */ +public class SignUpActivity extends Activity { + /** Log tag. */ + private static final String LOG_TAG = SignUpActivity.class.getSimpleName(); + + private SignUpView signUpView; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sign_up); + + signUpView = (SignUpView) findViewById(R.id.signup_view); + + } + + @Override + protected void onResume() { + super.onResume(); + } + + /** + * Retrieve input and return to caller. + * @param view the Android View + */ + public void signUp(final View view) { + + final String username = signUpView.getUserName(); + final String password = signUpView.getPassword(); + final String givenName = signUpView.getGivenName(); + final String email = signUpView.getEmail(); + final String phone = signUpView.getPhone(); + + Log.d(LOG_TAG, "username = " + username); + Log.d(LOG_TAG, "given_name = " + givenName); + Log.d(LOG_TAG, "email = " + email); + Log.d(LOG_TAG, "phone = " + phone); + + final Intent intent = new Intent(); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.USERNAME, username); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PASSWORD, password); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.GIVEN_NAME, givenName); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.EMAIL_ADDRESS, email); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.PHONE_NUMBER, phone); + + setResult(RESULT_OK, intent); + + finish(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmActivity.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmActivity.java new file mode 100644 index 00000000000..0a74ef6ab76 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmActivity.java @@ -0,0 +1,71 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import com.amazonaws.mobile.auth.userpools.R; + +/** + * Activity to prompt for sign-up confirmation information. + */ +public class SignUpConfirmActivity extends Activity { + /** Log tag. */ + private static final String LOG_TAG = SignUpConfirmActivity.class.getSimpleName(); + private SignUpConfirmView signUpConfirmView; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sign_up_confirm); + + final String username = getIntent().getStringExtra( + CognitoUserPoolsSignInProvider.AttributeKeys.USERNAME); + + signUpConfirmView = (SignUpConfirmView) findViewById(R.id.signup_confirm_view); + signUpConfirmView.getUserNameEditText().setText(username); + signUpConfirmView.getConfirmCodeEditText().requestFocus(); + } + + /** + * Retrieve input and return to caller. + * @param view the Android View + */ + public void confirmAccount(final View view) { + + final String username = + signUpConfirmView.getUserNameEditText().getText().toString(); + final String verificationCode = + signUpConfirmView.getConfirmCodeEditText().getText().toString(); + + Log.d(LOG_TAG, "username = " + username); + Log.d(LOG_TAG, "verificationCode = " + verificationCode); + + final Intent intent = new Intent(); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.USERNAME, username); + intent.putExtra(CognitoUserPoolsSignInProvider.AttributeKeys.VERIFICATION_CODE, verificationCode); + + setResult(RESULT_OK, intent); + + finish(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmView.java new file mode 100644 index 00000000000..751148c6ffd --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpConfirmView.java @@ -0,0 +1,154 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.amazonaws.mobile.config.AWSConfiguration; + +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils; +import com.amazonaws.mobile.auth.core.signin.ui.SplitBackgroundDrawable; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_COLOR; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_CORNER_RADIUS; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_SIDE_MARGIN_RATIO; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.MAX_FORM_WIDTH_IN_PIXELS; + +/** + * This view presents the confirmation screen for user sign up. + */ +public class SignUpConfirmView extends LinearLayout { + private FormView confirmForm; + private EditText userNameEditText; + private EditText confirmCodeEditText; + private SplitBackgroundDrawable splitBackgroundDrawable; + + /** + * Constructs the SignUpConfirm View. + * @param context The activity context. + */ + public SignUpConfirmView(final Context context) { + this(context, null); + } + + /** + * Constructs the SignUpConfirm View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + */ + public SignUpConfirmView(final Context context, @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructs the SignUpConfirm View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + * @param defStyleAttr The resource identifier for the default style attribute. + */ + public SignUpConfirmView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOrientation(VERTICAL); + final int backgroundColor; + if (isInEditMode()) { + backgroundColor = Color.DKGRAY; + } else { + final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.SignUpConfirmView); + backgroundColor = styledAttributes.getInt(R.styleable.SignUpConfirmView_signUpConfirmViewBackgroundColor, Color.DKGRAY); + styledAttributes.recycle(); + } + + splitBackgroundDrawable = new SplitBackgroundDrawable(0, getBackgroundColor(context, backgroundColor)); + } + + private int getBackgroundColor(final Context context, final int defaultBackgroundColor) { + Intent intent = ((Activity) context).getIntent(); + return (int) (intent.getIntExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + defaultBackgroundColor)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + confirmForm = (FormView) findViewById(R.id.signup_confirm_form); + userNameEditText = confirmForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT| InputType.TYPE_TEXT_VARIATION_PERSON_NAME, + getContext().getString(R.string.username_text)); + + confirmCodeEditText = confirmForm.addFormField(getContext(), + InputType.TYPE_CLASS_NUMBER, + getContext().getString(R.string.sign_up_confirm_code)); + + + setupConfirmButtonColor(); + } + + private void setupConfirmButtonColor() { + final Button confirmButton = (Button) findViewById(R.id.confirm_account_button); + confirmButton.setBackgroundDrawable(DisplayUtils.getRoundedRectangleBackground( + FORM_BUTTON_CORNER_RADIUS, FORM_BUTTON_COLOR)); + final LayoutParams signUpButtonLayoutParams = (LayoutParams) confirmButton.getLayoutParams(); + signUpButtonLayoutParams.setMargins( + confirmForm.getFormShadowMargin(), + signUpButtonLayoutParams.topMargin, + confirmForm.getFormShadowMargin(), + signUpButtonLayoutParams.bottomMargin); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = Math.min((int)(parentWidth * FORM_SIDE_MARGIN_RATIO), MAX_FORM_WIDTH_IN_PIXELS); + super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setupSplitBackground(); + } + + private void setupSplitBackground() { + splitBackgroundDrawable.setSplitPointDistanceFromTop(confirmForm.getTop() + + (confirmForm.getMeasuredHeight()/2)); + ((ViewGroup)getParent()).setBackgroundDrawable(splitBackgroundDrawable); + } + + public EditText getUserNameEditText() { + return userNameEditText; + } + + public EditText getConfirmCodeEditText() { + return confirmCodeEditText; + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpView.java new file mode 100644 index 00000000000..2ec6f8e1b69 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/SignUpView.java @@ -0,0 +1,198 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.amazonaws.mobile.config.AWSConfiguration; +import com.amazonaws.mobile.auth.core.IdentityManager; +import com.amazonaws.mobile.auth.core.signin.SignInManager; +import com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils; +import com.amazonaws.mobile.auth.core.signin.ui.SplitBackgroundDrawable; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_COLOR; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_CORNER_RADIUS; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_SIDE_MARGIN_RATIO; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.MAX_FORM_WIDTH_IN_PIXELS; + +/** + * The view that handles user sign-up for Cognito User Pools. + */ +public class SignUpView extends LinearLayout { + private TextView signUpMessage; + private Button signUpButton; + private FormView signUpForm; + private EditText userNameEditText; + private EditText passwordEditText; + private EditText givenNameEditText; + private EditText emailEditText; + private EditText phoneEditText; + private SplitBackgroundDrawable splitBackgroundDrawable; + + /** + * Constructs the SignUp View. + * @param context The activity context. + */ + public SignUpView(final Context context) { + this(context, null); + } + + /** + * Constructs the SignUp View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + */ + public SignUpView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + /** + * Constructs the SignUp View. + * @param context The activity context. + * @param attrs The Attribute Set for the view from which the resources can be accessed. + * @param defStyleAttr The resource identifier for the default style attribute. + */ + public SignUpView(final Context context, final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + setOrientation(VERTICAL); + + final int backgroundColor; + if (isInEditMode()) { + backgroundColor = Color.DKGRAY; + } else { + final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.SignUpView); + backgroundColor = styledAttributes.getInt(R.styleable.SignUpView_signUpViewBackgroundColor, Color.DKGRAY); + styledAttributes.recycle(); + } + + splitBackgroundDrawable = new SplitBackgroundDrawable(0, getBackgroundColor(context, backgroundColor)); + } + + private int getBackgroundColor(final Context context, final int defaultBackgroundColor) { + Intent intent = ((Activity) context).getIntent(); + return (int) (intent.getIntExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + defaultBackgroundColor)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + this.signUpForm = (FormView) findViewById(R.id.signup_form); + userNameEditText = signUpForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT| InputType.TYPE_TEXT_VARIATION_PERSON_NAME, + getContext().getString(R.string.username_text)); + + passwordEditText = signUpForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD, + getContext().getString(R.string.sign_in_password)); + + givenNameEditText = signUpForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT| InputType.TYPE_TEXT_VARIATION_PERSON_NAME, + getContext().getString(R.string.given_name_text)); + + emailEditText = signUpForm.addFormField(getContext(), + InputType.TYPE_CLASS_TEXT| InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, + getContext().getString(R.string.email_address_text)); + + phoneEditText = signUpForm.addFormField(getContext(), + InputType.TYPE_CLASS_PHONE, + getContext().getString(R.string.phone_number_text)); + + this.signUpMessage = (TextView) findViewById(R.id.signup_message); + this.signUpButton = (Button) findViewById(R.id.signup_button); + setupSignUpButtonBackground(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = Math.min((int)(parentWidth * FORM_SIDE_MARGIN_RATIO), MAX_FORM_WIDTH_IN_PIXELS); + super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), heightMeasureSpec); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + setupSplitBackground(); + } + + private void setupSignUpButtonBackground() { + signUpButton.setBackgroundDrawable(DisplayUtils.getRoundedRectangleBackground( + FORM_BUTTON_CORNER_RADIUS, FORM_BUTTON_COLOR)); + LayoutParams signUpButtonLayoutParams = (LayoutParams) signUpButton.getLayoutParams(); + signUpButtonLayoutParams.setMargins( + signUpForm.getFormShadowMargin(), + signUpButtonLayoutParams.topMargin, + signUpForm.getFormShadowMargin(), + signUpButtonLayoutParams.bottomMargin); + } + + private void setupSplitBackground() { + splitBackgroundDrawable.setSplitPointDistanceFromTop( + signUpForm.getTop() + (signUpForm.getMeasuredHeight()/2)); + ((ViewGroup) getParent()).setBackgroundDrawable(splitBackgroundDrawable); + } + + /** + * @return the user's user name entered in the form. + */ + public String getUserName() { + return userNameEditText.getText().toString(); + } + + /** + * @return the user's password entered in the form. + */ + public String getPassword() { + return passwordEditText.getText().toString(); + } + + /** + * @return the user's given name entered in the form. + */ + public String getGivenName() { + return givenNameEditText.getText().toString(); + } + + /** + * @return the user's email entered in the form. + */ + public String getEmail() { + return emailEditText.getText().toString(); + } + + /** + * @return the user's phone number entered in the form. + */ + public String getPhone() { + return phoneEditText.getText().toString(); + } +} diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolFormConstants.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolFormConstants.java new file mode 100644 index 00000000000..8023f21bdb8 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolFormConstants.java @@ -0,0 +1,37 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; + +/** + * Common constants for user pool forms. + */ +public final class UserPoolFormConstants { + /** Form Button Color. */ + public static final int FORM_BUTTON_COLOR = 0xff4599ff; + + /** Form button radius in pixels. */ + public static final int FORM_BUTTON_CORNER_RADIUS = dp(5); + + /** Ratio for the form size relative to the parent view. */ + public static final double FORM_SIDE_MARGIN_RATIO = 0.85; + + /** Maximum width of the form in pixels. */ + public static final int MAX_FORM_WIDTH_IN_PIXELS = dp(300); +} \ No newline at end of file diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolSignInView.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolSignInView.java new file mode 100644 index 00000000000..b4534a875e1 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/UserPoolSignInView.java @@ -0,0 +1,238 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.auth.userpools; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Color; +import android.support.annotation.Nullable; +import android.text.InputType; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; + +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.amazonaws.mobile.auth.core.signin.SignInManager; + +import com.amazonaws.mobile.auth.userpools.R; + +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.dp; +import static com.amazonaws.mobile.auth.core.signin.ui.DisplayUtils.getRoundedRectangleBackground; + +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_COLOR; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_BUTTON_CORNER_RADIUS; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.FORM_SIDE_MARGIN_RATIO; +import static com.amazonaws.mobile.auth.userpools.UserPoolFormConstants.MAX_FORM_WIDTH_IN_PIXELS; + +/** + * User Pools Sign-in Control. This view presents a form to handle user sign-in. + * It also presents choices for creating a new account or retrieving a forgotten password. + */ +public class UserPoolSignInView extends LinearLayout { + + /** Log tag. */ + private static final String LOG_TAG = UserPoolSignInView.class.getSimpleName(); + + /** Create Account Text View */ + private TextView signUpTextView; + + /** Forgot Password Text View */ + private TextView forgotPasswordTextView; + + /** The credentials form that styles the username and password fields. */ + private FormView credentialsFormView; + + /** The Username field. */ + private EditText userNameEditText; + + /** The Password field. */ + private EditText passwordEditText; + + /** The sign in button. */ + private Button signInButton; + + /** Flag for whether the control has been intitialized. */ + private boolean isInitialized; + + /** Background Color. */ + private int backgroundColor; + + /** Default Background color used by the views. */ + private static final int DEFAULT_BACKGROUND_COLOR = Color.DKGRAY; + + public UserPoolSignInView(final Context context) { + this(context, null); + } + + public UserPoolSignInView(final Context context, @Nullable final AttributeSet attrs) { + this(context, attrs, 0); + } + + public UserPoolSignInView(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.setOrientation(VERTICAL); + this.setGravity(Gravity.CENTER); + this.setId(R.id.user_pool_sign_in_view_id); + + setupCredentialsForm(context); + setupSignInButton(context); + setupLayoutForSignUpAndForgotPassword(context); + setupBackgroundColor(context); + } + + private void initializeIfNecessary() { + if (isInitialized) { + return; + } + isInitialized = true; + + if (isInEditMode()) { + return; + } + + try { + final SignInManager signInManager = SignInManager.getInstance(); + signInManager.initializeSignInButton(CognitoUserPoolsSignInProvider.class, signInButton); + } catch (final Exception exception) { + Log.e(LOG_TAG, "Cannot initialize the SignInButton. Please check if IdentityManager :" + + " startUpAuth and setUpToAuthenticate are invoked", exception); + } + } + + private void setupCredentialsForm(final Context context) { + credentialsFormView = new FormView(context); + final LinearLayout.LayoutParams formViewLayoutParams + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + + userNameEditText = credentialsFormView.addFormField(context, + InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, + // User Pools requires sign in with the username or verified channel. + // Mobile Hub does not set up email verification because it requires SES verification. + // Hence, prompt customers to login using the username or phone number. + context.getString(R.string.sign_in_username)); + passwordEditText = credentialsFormView.addFormField(context, + InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_VARIATION_PASSWORD, + context.getString(R.string.sign_in_password)); + + this.addView(credentialsFormView, formViewLayoutParams); + } + + private void setupSignInButton(final Context context) { + signInButton = new Button(context); + signInButton.setTextColor(Color.WHITE); + signInButton.setText(context.getString(R.string.sign_in_button_text)); + signInButton.setAllCaps(false); + signInButton.setBackgroundDrawable( + getRoundedRectangleBackground(FORM_BUTTON_CORNER_RADIUS, FORM_BUTTON_COLOR)); + + final Resources resources = getResources(); + final LinearLayout.LayoutParams signInButtonLayoutParams = new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(R.dimen.sign_in_button_height)); + final int signInButtonMarginTopBottom + = resources.getDimensionPixelSize(R.dimen.user_pools_sign_in_button_margin_top_bottom); + signInButtonLayoutParams.setMargins( + credentialsFormView.getFormShadowMargin(), + signInButtonMarginTopBottom + credentialsFormView.getFormShadowMargin(), + credentialsFormView.getFormShadowMargin(), + 0); + this.addView(signInButton, signInButtonLayoutParams); + } + + private void setupLayoutForSignUpAndForgotPassword(final Context context) { + final LinearLayout layoutForSignUpAndForgotPassword = new LinearLayout(context); + layoutForSignUpAndForgotPassword.setOrientation(HORIZONTAL); + final LinearLayout.LayoutParams layoutParamsForSignUpAndForgotPassword + = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + + layoutParamsForSignUpAndForgotPassword.setMargins(credentialsFormView.getFormShadowMargin(), + dp(10), credentialsFormView.getFormShadowMargin(), 0); + layoutParamsForSignUpAndForgotPassword.gravity = Gravity.CENTER_HORIZONTAL; + + signUpTextView = new TextView(context); + signUpTextView.setText(R.string.sign_in_new_account); + signUpTextView.setTextAppearance(context, android.R.style.TextAppearance_Small); + signUpTextView.setGravity(Gravity.START); + signUpTextView.setTextColor(FORM_BUTTON_COLOR); + final LinearLayout.LayoutParams layoutParamsForNewAccountText + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + layoutParamsForNewAccountText.weight = 1; + layoutForSignUpAndForgotPassword.addView(signUpTextView, layoutParamsForNewAccountText); + + forgotPasswordTextView = new TextView(context); + forgotPasswordTextView.setText(R.string.sign_in_forgot_password); + forgotPasswordTextView.setTextAppearance(context, android.R.style.TextAppearance_Small); + forgotPasswordTextView.setGravity(Gravity.END); + forgotPasswordTextView.setTextColor(FORM_BUTTON_COLOR); + final LinearLayout.LayoutParams layoutParamsForForgotPassword + = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + layoutParamsForForgotPassword.weight = 1; + layoutForSignUpAndForgotPassword.addView(forgotPasswordTextView, layoutParamsForForgotPassword); + + this.addView(layoutForSignUpAndForgotPassword, layoutParamsForSignUpAndForgotPassword); + } + + private void setupBackgroundColor(final Context context) { + Intent intent = ((Activity) context).getIntent(); + this.backgroundColor = (int) (intent.getIntExtra(CognitoUserPoolsSignInProvider.AttributeKeys.BACKGROUND_COLOR, + DEFAULT_BACKGROUND_COLOR)); + } + + /** + * Gets the Background Color passed in by the UI. + * @return + */ + public int getBackgroundColor() { + return this.backgroundColor; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + final int maxWidth = Math.min((int)(parentWidth * FORM_SIDE_MARGIN_RATIO), MAX_FORM_WIDTH_IN_PIXELS); + super.onMeasure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), heightMeasureSpec); + initializeIfNecessary(); + } + + public TextView getSignUpTextView() { + return signUpTextView; + } + + public TextView getForgotPasswordTextView() { + return forgotPasswordTextView; + } + + public String getEnteredUserName() { + return userNameEditText.getText().toString(); + } + + public String getEnteredPassword() { + return passwordEditText.getText().toString(); + } + + public FormView getCredentialsFormView() { + return credentialsFormView; + } +} \ No newline at end of file diff --git a/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/package-info.java b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/package-info.java new file mode 100644 index 00000000000..01bd0ff1dd4 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/java/com/amazonaws/mobile/auth/userpools/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2013-2017 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Package Info for UserPools. + */ +package com.amazonaws.mobile.auth.userpools; \ No newline at end of file diff --git a/aws-android-sdk-auth-userpools/src/main/res/layout/activity_forgot_password.xml b/aws-android-sdk-auth-userpools/src/main/res/layout/activity_forgot_password.xml new file mode 100644 index 00000000000..a9e47c00427 --- /dev/null +++ b/aws-android-sdk-auth-userpools/src/main/res/layout/activity_forgot_password.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + +