com.auth0.android.lock.LockActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.auth0.android.lock.LockActivity.java

Source

/*
 * LockActivity.java
 *
 * Copyright (c) 2016 Auth0 (http://auth0.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.auth0.android.lock;

import android.app.Dialog;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;

import com.auth0.android.authentication.AuthenticationAPIClient;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.authentication.ParameterBuilder;
import com.auth0.android.authentication.request.SignUpRequest;
import com.auth0.android.callback.AuthenticationCallback;
import com.auth0.android.lock.errors.AuthenticationError;
import com.auth0.android.lock.errors.LoginErrorMessageBuilder;
import com.auth0.android.lock.errors.SignUpErrorMessageBuilder;
import com.auth0.android.lock.events.DatabaseChangePasswordEvent;
import com.auth0.android.lock.events.DatabaseLoginEvent;
import com.auth0.android.lock.events.DatabaseSignUpEvent;
import com.auth0.android.lock.events.FetchApplicationEvent;
import com.auth0.android.lock.events.LockMessageEvent;
import com.auth0.android.lock.events.OAuthLoginEvent;
import com.auth0.android.lock.internal.configuration.ApplicationFetcher;
import com.auth0.android.lock.internal.configuration.Configuration;
import com.auth0.android.lock.internal.configuration.Connection;
import com.auth0.android.lock.internal.configuration.Options;
import com.auth0.android.lock.provider.AuthResolver;
import com.auth0.android.lock.views.ClassicLockView;
import com.auth0.android.provider.AuthCallback;
import com.auth0.android.provider.AuthProvider;
import com.auth0.android.provider.WebAuthProvider;
import com.auth0.android.request.AuthenticationRequest;
import com.auth0.android.result.Credentials;
import com.auth0.android.result.DatabaseUser;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.otto.Bus;
import com.squareup.otto.Subscribe;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LockActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {

    private static final String TAG = LockActivity.class.getSimpleName();
    private static final String KEY_VERIFICATION_CODE = "mfa_code";
    private static final String KEY_LOGIN_HINT = "login_hint";
    private static final long RESULT_MESSAGE_DURATION = 3000;
    private static final int WEB_AUTH_REQUEST_CODE = 200;
    private static final int CUSTOM_AUTH_REQUEST_CODE = 201;
    private static final int PERMISSION_REQUEST_CODE = 202;

    private ApplicationFetcher applicationFetcher;
    private Configuration configuration;
    private Options options;
    private Handler handler;

    private ClassicLockView lockView;
    private TextView resultMessage;

    private AuthProvider currentProvider;
    private WebProvider webProvider;

    private LoginErrorMessageBuilder loginErrorBuilder;
    private SignUpErrorMessageBuilder signUpErrorBuilder;
    private DatabaseLoginEvent lastDatabaseLogin;

    @SuppressWarnings("unused")
    public LockActivity() {
    }

    @VisibleForTesting
    LockActivity(Configuration configuration, Options options, ClassicLockView lockView, WebProvider webProvider) {
        this.configuration = configuration;
        this.options = options;
        this.lockView = lockView;
        this.webProvider = webProvider;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!hasValidLaunchConfig()) {
            return;
        }

        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        Bus lockBus = new Bus();
        lockBus.register(this);
        handler = new Handler(getMainLooper());
        webProvider = new WebProvider(options);

        setContentView(R.layout.com_auth0_lock_activity_lock);
        resultMessage = (TextView) findViewById(R.id.com_auth0_lock_result_message);
        ScrollView rootView = (ScrollView) findViewById(R.id.com_auth0_lock_content);
        lockView = new ClassicLockView(this, lockBus, options.getTheme());
        RelativeLayout.LayoutParams lockViewParams = new RelativeLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        lockView.setLayoutParams(lockViewParams);
        rootView.addView(lockView);

        loginErrorBuilder = new LoginErrorMessageBuilder(R.string.com_auth0_lock_db_login_error_message,
                R.string.com_auth0_lock_db_login_error_invalid_credentials_message);
        signUpErrorBuilder = new SignUpErrorMessageBuilder();

        lockBus.post(new FetchApplicationEvent());
    }

    private boolean hasValidLaunchConfig() {
        String errorDescription = null;
        if (!hasValidOptions()) {
            errorDescription = "Configuration is not valid and the Activity will finish.";
        }
        if (!hasValidTheme()) {
            errorDescription = "You need to use a Lock.Theme theme (or descendant) with this Activity.";
        }
        if (errorDescription == null) {
            return true;
        }
        Intent intent = new Intent(Constants.INVALID_CONFIGURATION_ACTION);
        intent.putExtra(Constants.ERROR_EXTRA, errorDescription);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        finish();
        return false;
    }

    private boolean hasValidTheme() {
        TypedArray a = getTheme().obtainStyledAttributes(R.styleable.Lock_Theme);
        boolean validTheme = a.hasValue(R.styleable.Lock_Theme_Auth0_HeaderLogo);
        a.recycle();
        return validTheme;
    }

    private boolean hasValidOptions() {
        options = getIntent().getParcelableExtra(Constants.OPTIONS_EXTRA);
        if (options == null) {
            Log.e(TAG, "Lock Options are missing in the received Intent and LockActivity will not launch. "
                    + "Use the PasswordlessLock.Builder to generate a valid Intent.");
            return false;
        }

        boolean launchedForResult = getCallingActivity() != null;
        if (launchedForResult) {
            Log.e(TAG, "You're not allowed to start Lock with startActivityForResult.");
            return false;
        }
        boolean launchedAsSingleTask = (getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0;
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
            //TODO: Document this case for users on <= KITKAT, as they will not receive this warning.
            if (options.useBrowser() && !launchedAsSingleTask) {
                Log.e(TAG, "Please, check that you have specified launchMode 'singleTask' in the AndroidManifest.");
                return false;
            }
        }
        return true;
    }

    @Override
    public void onBackPressed() {
        if (lockView.onBackPressed() || !options.isClosable()) {
            return;
        }

        Log.v(TAG, "User had just closed the activity.");
        Intent intent = new Intent(Constants.CANCELED_ACTION);
        LocalBroadcastManager.getInstance(LockActivity.this).sendBroadcast(intent);
        super.onBackPressed();
    }

    private void deliverAuthenticationResult(Credentials credentials) {
        Intent intent = new Intent(Constants.AUTHENTICATION_ACTION);
        intent.putExtra(Constants.ID_TOKEN_EXTRA, credentials.getIdToken());
        intent.putExtra(Constants.ACCESS_TOKEN_EXTRA, credentials.getAccessToken());
        intent.putExtra(Constants.REFRESH_TOKEN_EXTRA, credentials.getRefreshToken());
        intent.putExtra(Constants.TOKEN_TYPE_EXTRA, credentials.getType());
        intent.putExtra(Constants.EXPIRES_IN_EXTRA, credentials.getExpiresIn());

        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        finish();
    }

    private void deliverSignUpResult(DatabaseUser result) {
        Intent intent = new Intent(Constants.SIGN_UP_ACTION);
        intent.putExtra(Constants.EMAIL_EXTRA, result.getEmail());
        intent.putExtra(Constants.USERNAME_EXTRA, result.getEmail());

        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        finish();
    }

    private void showSuccessMessage(String message) {
        resultMessage.setBackgroundColor(
                ContextCompat.getColor(this, R.color.com_auth0_lock_result_message_success_background));
        resultMessage.setVisibility(View.VISIBLE);
        resultMessage.setText(message);
        lockView.showProgress(false);
        handler.removeCallbacks(resultMessageHider);
        handler.postDelayed(resultMessageHider, RESULT_MESSAGE_DURATION);
    }

    private void showErrorMessage(String message) {
        resultMessage.setBackgroundColor(
                ContextCompat.getColor(this, R.color.com_auth0_lock_result_message_error_background));
        resultMessage.setVisibility(View.VISIBLE);
        resultMessage.setText(message);
        lockView.showProgress(false);
        handler.removeCallbacks(resultMessageHider);
        handler.postDelayed(resultMessageHider, RESULT_MESSAGE_DURATION);
    }

    private Runnable resultMessageHider = new Runnable() {
        @Override
        public void run() {
            resultMessage.setVisibility(View.GONE);
        }
    };

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (currentProvider != null) {
            currentProvider.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case WEB_AUTH_REQUEST_CODE:
            lockView.showProgress(false);
            webProvider.resume(requestCode, resultCode, data);
            break;
        case CUSTOM_AUTH_REQUEST_CODE:
            lockView.showProgress(false);
            if (currentProvider != null) {
                currentProvider.authorize(requestCode, resultCode, data);
                currentProvider = null;
            }
            break;
        default:
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        lockView.showProgress(false);
        if (webProvider.resume(intent)) {
            return;
        } else if (currentProvider != null) {
            currentProvider.authorize(intent);
            currentProvider = null;
            return;
        }
        super.onNewIntent(intent);
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onFetchApplicationRequest(FetchApplicationEvent event) {
        if (applicationFetcher == null) {
            applicationFetcher = new ApplicationFetcher(options.getAccount(), new OkHttpClient());
            applicationFetcher.fetch(applicationCallback);
        }
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onLockMessage(final LockMessageEvent event) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                showErrorMessage(getString(event.getMessageRes()));
            }
        });
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onOAuthAuthenticationRequest(OAuthLoginEvent event) {
        final String connection = event.getConnection();

        if (event.useActiveFlow()) {
            lockView.showProgress(true);
            Log.d(TAG, "Using the /ro endpoint for this OAuth Login Request");
            AuthenticationRequest request = options.getAuthenticationAPIClient()
                    .login(event.getUsername(), event.getPassword(), connection)
                    .addAuthenticationParameters(options.getAuthenticationParameters());
            if (options.getScope() != null) {
                request.setScope(options.getScope());
            }
            if (options.getAudience() != null && options.getAccount().isOIDCConformant()) {
                request.setAudience(options.getAudience());
            }
            request.start(authCallback);
            return;
        }

        Log.v(TAG, "Looking for a provider to use /authorize with the connection " + connection);
        currentProvider = AuthResolver.providerFor(event.getStrategy(), connection);
        if (currentProvider != null) {
            HashMap<String, Object> authParameters = new HashMap<>(options.getAuthenticationParameters());
            final String connectionScope = options.getConnectionsScope().get(connection);
            if (connectionScope != null) {
                authParameters.put(Constants.CONNECTION_SCOPE_KEY, connectionScope);
            }
            final String scope = options.getScope();
            if (scope != null) {
                authParameters.put(ParameterBuilder.SCOPE_KEY, scope);
            }
            final String audience = options.getAudience();
            if (audience != null && options.getAccount().isOIDCConformant()) {
                authParameters.put(ParameterBuilder.AUDIENCE_KEY, audience);
            }
            if (!TextUtils.isEmpty(event.getUsername())) {
                authParameters.put(KEY_LOGIN_HINT, event.getUsername());
            }
            currentProvider.setParameters(authParameters);
            currentProvider.start(this, authProviderCallback, PERMISSION_REQUEST_CODE, CUSTOM_AUTH_REQUEST_CODE);
            return;
        }

        Map<String, Object> extraAuthParameters = null;
        if (!TextUtils.isEmpty(event.getUsername())) {
            extraAuthParameters = Collections.singletonMap(KEY_LOGIN_HINT, (Object) event.getUsername());
        }
        Log.d(TAG,
                "Couldn't find an specific provider, using the default: " + WebAuthProvider.class.getSimpleName());
        webProvider.start(this, connection, extraAuthParameters, authProviderCallback, WEB_AUTH_REQUEST_CODE);
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onDatabaseAuthenticationRequest(DatabaseLoginEvent event) {
        if (configuration.getDatabaseConnection() == null) {
            Log.w(TAG, "There is no default Database connection to authenticate with");
            return;
        }

        lockView.showProgress(true);
        lastDatabaseLogin = event;
        AuthenticationAPIClient apiClient = options.getAuthenticationAPIClient();
        final HashMap<String, Object> parameters = new HashMap<>(options.getAuthenticationParameters());
        if (event.getVerificationCode() != null) {
            parameters.put(KEY_VERIFICATION_CODE, event.getVerificationCode());
        }
        final String connection = configuration.getDatabaseConnection().getName();
        AuthenticationRequest request = apiClient.login(event.getUsernameOrEmail(), event.getPassword(), connection)
                .addAuthenticationParameters(parameters);
        if (options.getScope() != null) {
            request.setScope(options.getScope());
        }
        if (options.getAudience() != null && options.getAccount().isOIDCConformant()) {
            request.setAudience(options.getAudience());
        }
        request.start(authCallback);
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onDatabaseAuthenticationRequest(DatabaseSignUpEvent event) {
        if (configuration.getDatabaseConnection() == null) {
            Log.w(TAG, "There is no default Database connection to authenticate with");
            return;
        }

        AuthenticationAPIClient apiClient = options.getAuthenticationAPIClient();
        final String connection = configuration.getDatabaseConnection().getName();
        lockView.showProgress(true);

        if (configuration.loginAfterSignUp()) {
            Map<String, Object> authParameters = new HashMap<>(options.getAuthenticationParameters());
            SignUpRequest request = event.getSignUpRequest(apiClient, connection)
                    .addAuthenticationParameters(authParameters);
            if (options.getScope() != null) {
                request.setScope(options.getScope());
            }
            if (options.getAudience() != null && options.getAccount().isOIDCConformant()) {
                request.setAudience(options.getAudience());
            }
            request.start(authCallback);
        } else {
            event.getCreateUserRequest(apiClient, connection).start(createCallback);
        }
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onDatabaseAuthenticationRequest(DatabaseChangePasswordEvent event) {
        if (configuration.getDatabaseConnection() == null) {
            Log.w(TAG, "There is no default Database connection to authenticate with");
            return;
        }

        lockView.showProgress(true);
        AuthenticationAPIClient apiClient = options.getAuthenticationAPIClient();
        final String connection = configuration.getDatabaseConnection().getName();
        apiClient.resetPassword(event.getEmail(), connection).start(changePwdCallback);
    }

    //Callbacks
    private com.auth0.android.callback.AuthenticationCallback<List<Connection>> applicationCallback = new AuthenticationCallback<List<Connection>>() {
        @Override
        public void onSuccess(final List<Connection> connections) {
            configuration = new Configuration(connections, options);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    lockView.configure(configuration);
                }
            });
            applicationFetcher = null;
        }

        @Override
        public void onFailure(final AuthenticationException error) {
            Log.e(TAG, "Failed to fetch the application: " + error.getMessage(), error);
            applicationFetcher = null;
            handler.post(new Runnable() {
                @Override
                public void run() {
                    lockView.configure(null);
                }
            });
        }
    };

    private AuthCallback authProviderCallback = new AuthCallback() {
        @Override
        public void onFailure(@NonNull final Dialog dialog) {
            Log.e(TAG, "Failed to authenticate the user. A dialog is going to be shown with more information.");
            dialog.show();
            handler.post(new Runnable() {
                @Override
                public void run() {
                    dialog.show();
                }
            });
        }

        @Override
        public void onFailure(final AuthenticationException exception) {
            final AuthenticationError authError = loginErrorBuilder.buildFrom(exception);
            final String message = authError.getMessage(LockActivity.this);
            Log.e(TAG, "Failed to authenticate the user: " + message, exception);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    showErrorMessage(message);
                }
            });
        }

        @Override
        public void onSuccess(@NonNull final Credentials credentials) {
            deliverAuthenticationResult(credentials);
        }
    };

    private AuthenticationCallback<Credentials> authCallback = new AuthenticationCallback<Credentials>() {
        @Override
        public void onSuccess(Credentials credentials) {
            deliverAuthenticationResult(credentials);
            lastDatabaseLogin = null;
        }

        @Override
        public void onFailure(final AuthenticationException error) {
            Log.e(TAG, "Failed to authenticate the user: " + error.getMessage(), error);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    lockView.showProgress(false);

                    final AuthenticationError authError = loginErrorBuilder.buildFrom(error);
                    if (error.isMultifactorRequired() || error.isMultifactorEnrollRequired()) {
                        lockView.showMFACodeForm(lastDatabaseLogin);
                        return;
                    }
                    String message = authError.getMessage(LockActivity.this);
                    showErrorMessage(message);
                }
            });
        }
    };

    private AuthenticationCallback<DatabaseUser> createCallback = new AuthenticationCallback<DatabaseUser>() {
        @Override
        public void onSuccess(final DatabaseUser user) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    deliverSignUpResult(user);
                }
            });
        }

        @Override
        public void onFailure(final AuthenticationException error) {
            Log.e(TAG, "Failed to create the user: " + error.getMessage(), error);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String message = signUpErrorBuilder.buildFrom(error).getMessage(LockActivity.this);
                    showErrorMessage(message);
                }
            });
        }
    };

    private AuthenticationCallback<Void> changePwdCallback = new AuthenticationCallback<Void>() {
        @Override
        public void onSuccess(Void payload) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    showSuccessMessage(getString(R.string.com_auth0_lock_db_change_password_message_success));
                    if (options.allowLogIn() || options.allowSignUp()) {
                        lockView.showChangePasswordForm(false);
                    }
                }
            });

        }

        @Override
        public void onFailure(AuthenticationException error) {
            Log.e(TAG, "Failed to reset the user password: " + error.getMessage(), error);
            handler.post(new Runnable() {
                @Override
                public void run() {
                    String message = new AuthenticationError(
                            R.string.com_auth0_lock_db_message_change_password_error).getMessage(LockActivity.this);
                    showErrorMessage(message);
                }
            });
        }
    };

}