com.amazon.android.contentbrowser.helper.AuthHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.amazon.android.contentbrowser.helper.AuthHelper.java

Source

/**
 * Copyright 2015-2016 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.
 * A copy of the License is located at
 *
 *     http://aws.amazon.com/apache2.0/
 *
 * or in the "license" file accompanying this file. This file 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.amazon.android.contentbrowser.helper;

import com.amazon.android.contentbrowser.ContentBrowser;
import com.amazon.android.contentbrowser.R;
import com.amazon.android.module.ModuleManager;
import com.amazon.android.ui.constants.PreferencesConstants;
import com.amazon.android.ui.fragments.ErrorDialogFragment;
import com.amazon.android.ui.fragments.LogoutSettingsFragment;
import com.amazon.android.utils.ErrorUtils;
import com.amazon.android.utils.GlideHelper;
import com.amazon.android.utils.NetworkUtils;
import com.amazon.android.utils.Preferences;
import com.amazon.auth.AuthenticationConstants;
import com.amazon.auth.IAuthentication;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import com.github.droibit.rxactivitylauncher.RxLauncher;

import org.greenrobot.eventbus.EventBus;
import org.json.JSONArray;
import org.json.JSONObject;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import rx.Observable;
import rx.Subscriber;
import rx.operators.OperatorIfThen;

/**
 * Authentication helper class.
 */
public class AuthHelper {

    /**
     * Event class to represent Authorization events.
     */
    public static class AuthenticationStatusUpdateEvent {

        /**
         * User authentication flag.
         */
        private boolean mUserAuthenticated = false;

        /**
         * Constructor
         *
         * @param flag User authentication flag.
         */
        public AuthenticationStatusUpdateEvent(boolean flag) {

            mUserAuthenticated = flag;
        }

        /**
         * Returns true if the user is authentication after this event happened, false otherwise
         *
         * @return true if the user is authentication after this event happened, false otherwise
         */
        public boolean isUserAuthenticated() {

            return mUserAuthenticated;
        }
    }

    /**
     * Debug TAG.
     */
    private static final String TAG = AuthHelper.class.getSimpleName();

    /**
     * Request code MUST be a positive value, otherwise the result callback will not be
     * triggered!!!
     * So no 0xCAFEBABE form good old days :(
     */
    public static final int AUTH_ON_ACTIVITY_RESULT_REQUEST_CODE = 0x0AFEBABE;

    /**
     * The key to retrieve the "login later" preferences.
     */
    public static final String LOGIN_LATER_PREFERENCES_KEY = "LOGIN_LATER_PREFERENCES_KEY";

    /**
     * Result key.
     */
    public static final String RESULT = "RESULT";

    /**
     * Result from activity key.
     */
    public static final String RESULT_FROM_ACTIVITY = "RESULT_FROM_ACTIVITY";

    /**
     * String constant to get the white list object from the MVPD JSONObject.
     */
    private static final String MVPD_WHITE_LIST = "mvpdWhitelist";

    /**
     * String constant to get the logged-in image URL for the MVPD JSONObject.
     */
    private static final String LOGGED_IN_IMAGE = "loggedInImage";

    /**
     * String constant to compare the default MVPD string value in custom.xml to.
     */
    private static final String DEFAULT_MVPD_URL = "DEFAULT_MVPD_URL";

    /**
     * Authentication implementation reference.
     */
    private IAuthentication mIAuthentication;

    /**
     * Application context.
     */
    private final Context mAppContext;

    /**
     * Content browser reference.
     */
    private final ContentBrowser mContentBrowser;

    /**
     * RX Launcher instance.
     */
    private final RxLauncher mRxLauncher = RxLauncher.getInstance();

    /**
     * Authorized handler interface.
     */
    public interface IAuthorizedHandler {

        /**
         * On authorized listener.
         *
         * @param extra Extra result bundle.
         */
        void onAuthorized(Bundle extra);
    }

    /**
     * Constructor.
     *
     * @param context        Context.
     * @param contentBrowser Content browser.
     */
    public AuthHelper(Context context, ContentBrowser contentBrowser) {

        mAppContext = context.getApplicationContext();
        mContentBrowser = contentBrowser;

        // Get default Auth interface without creating a new one.
        try {
            mIAuthentication = (IAuthentication) ModuleManager.getInstance()
                    .getModule(IAuthentication.class.getSimpleName()).getImpl(true);
        } catch (Exception e) {
            Log.e(TAG, "No Auth Interface interface attached.", e);
        }

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(LogoutSettingsFragment.LOGOUT_BUTTON_BROADCAST_INTENT_ACTION);
        LocalBroadcastManager.getInstance(mAppContext).registerReceiver(mLocalBroadcastReceiver, intentFilter);
        //AuthHelper is initialized, broadcast initial authentication status
        isAuthenticated().subscribe(isAuthenticatedResultBundle -> {
            boolean result = isAuthenticatedResultBundle.getBoolean(AuthHelper.RESULT);
            broadcastAuthenticationStatus(result);
        });
    }

    /**
     * Method to broadcast authentication status, this includes informing content browser about the
     * new status and sending an Event bus event.
     *
     * @param authenticationStatus authentication status to broadcast
     */
    private void broadcastAuthenticationStatus(boolean authenticationStatus) {

        mContentBrowser
                .onAuthenticationStatusUpdateEvent(new AuthenticationStatusUpdateEvent(authenticationStatus));
        EventBus.getDefault().post(new AuthenticationStatusUpdateEvent(authenticationStatus));
    }

    /**
     * Get authentication interface.
     *
     * @return Authentication interface.
     */
    public IAuthentication getIAuthentication() {

        return mIAuthentication;
    }

    /**
     * Local broadcast receiver to listen to analytics events, useful for interface decoupled
     * access.
     */
    private final BroadcastReceiver mLocalBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            if (intent.getAction().equals(LogoutSettingsFragment.LOGOUT_BUTTON_BROADCAST_INTENT_ACTION)) {
                Log.d(TAG, "Got logout broadcast!!! : " + intent);
                logoutFromAccount(mAppContext);
            }
        }
    };

    /**
     * Handle success case of subscriber.
     *
     * @param subscriber Subscriber.
     * @param extras     Result bundle.
     */
    private void handleSuccessCase(Subscriber subscriber, Bundle extras) {

        extras.putBoolean(RESULT, true);
        if (!subscriber.isUnsubscribed()) {
            subscriber.onNext(extras);
        }
        subscriber.onCompleted();
    }

    /**
     * Handle failure case of subscriber.
     *
     * @param subscriber Subscriber.
     * @param extras     Result bundle.
     */
    private void handleFailureCase(Subscriber subscriber, Bundle extras) {

        extras.putBoolean(RESULT, false);
        if (!subscriber.isUnsubscribed()) {
            subscriber.onNext(extras);
        }
        subscriber.onCompleted();
    }

    /**
     * Logout observable.
     *
     * @return RX Observable.
     */
    public Observable<Bundle> logout() {

        Log.v(TAG, "logout called.");

        return Observable.create(subscriber -> {
            mIAuthentication.logout(mAppContext, new IAuthentication.ResponseHandler() {
                @Override
                public void onSuccess(Bundle extras) {

                    broadcastAuthenticationStatus(false);
                    Log.d(TAG, "Account logout success");
                    handleSuccessCase(subscriber, extras);
                }

                @Override
                public void onFailure(Bundle extras) {

                    Log.e(TAG, "Account logout failure");
                    handleFailureCase(subscriber, extras);
                }
            });
        });
    }

    /**
     * Is authenticated observable.
     *
     * @return RX Observable.
     */
    public Observable<Bundle> isAuthenticated() {

        return Observable.create(subscriber -> {
            // Check if user is logged in. If not, show authentication activity.
            mIAuthentication.isUserLoggedIn(mAppContext, new IAuthentication.ResponseHandler() {
                @Override
                public void onSuccess(Bundle extras) {

                    Log.d(TAG, "User is authenticated");
                    broadcastAuthenticationStatus(true);
                    handleSuccessCase(subscriber, extras);
                }

                @Override
                public void onFailure(Bundle extras) {

                    Log.e(TAG, "User is not authenticated");
                    broadcastAuthenticationStatus(false);
                    handleFailureCase(subscriber, extras);
                }
            });
        });
    }

    /**
     * Is authorized Observable.
     *
     * @return RX Observable.
     */
    public Observable<Bundle> isAuthorized() {

        return Observable.create(subscriber -> {
            // Check if user is logged in. If not, show authentication activity.
            mIAuthentication.isResourceAuthorized(mAppContext, "", new IAuthentication.ResponseHandler() {
                @Override
                public void onSuccess(Bundle extras) {

                    Log.d(TAG, "Resource Authorization " + "success");
                    handleSuccessCase(subscriber, extras);
                }

                @Override
                public void onFailure(Bundle extras) {

                    Log.e(TAG, "Resource Authorization " + "failed");
                    handleFailureCase(subscriber, extras);
                }
            });
        });
    }

    /**
     * Handle authentication activity result bundle.
     *
     * @param bundle Activity result bundle.
     */
    private void handleAuthenticationActivityResultBundle(Bundle bundle) {

        Bundle mvpdBundle = null;
        if (bundle != null) {
            mvpdBundle = (Bundle) bundle.get(AuthenticationConstants.MVPD_BUNDLE);
        }

        if (mvpdBundle == null) {
            Log.w(TAG, "No MVPD bundle found when handling authentication result");
            return;
        }
        String mvpd = mvpdBundle.getString(AuthenticationConstants.MVPD);

        String mvpdLogoUrl = mContentBrowser.getPoweredByLogoUrlByName(mvpd);
        if (mvpdLogoUrl == null || mvpdLogoUrl.isEmpty()) {
            Log.d(TAG, "MVPD url not found for:" + mvpd);
        }
        Preferences.setString(PreferencesConstants.MVPD_LOGO_URL, mvpdLogoUrl);
        Log.d(TAG, "MVPD in details:" + mvpd + " logo url:" + mvpdLogoUrl);

    }

    /**
     * Authenticate With Activity Observable.
     *
     * @return RX Observable.
     */
    public Observable<Bundle> authenticateWithActivity() {

        return mRxLauncher.from(mContentBrowser.getNavigator().getActiveActivity())
                .startActivityForResult(
                        getIAuthentication().getAuthenticationActivityIntent(
                                mContentBrowser.getNavigator().getActiveActivity()),
                        AuthHelper.AUTH_ON_ACTIVITY_RESULT_REQUEST_CODE, null)
                .map(activityResult -> {
                    Bundle resultBundle = null;
                    if (activityResult.isOk() && activityResult.data == null) {
                        resultBundle = new Bundle();
                    } else if (activityResult.data != null) {
                        resultBundle = activityResult.data.getExtras();
                    }
                    handleAuthenticationActivityResultBundle(resultBundle);

                    if (resultBundle != null) {
                        resultBundle.putBoolean(RESULT, activityResult.isOk());
                        broadcastAuthenticationStatus(activityResult.isOk());
                        resultBundle.putBoolean(RESULT_FROM_ACTIVITY, true);
                    }
                    return resultBundle;
                });
    }

    /**
     * Authenticate Observable.
     *
     * @return RX Observable.
     */
    private Observable<Bundle> authenticate() {

        return isAuthenticated().flatMap(
                // With isAuthenticated result bundle do
                isAuthenticatedResultBundle -> Observable.create(new OperatorIfThen<>(
                        // If isAuthenticated success then do
                        // isAuthorized.
                        () -> isAuthenticatedResultBundle.getBoolean(RESULT), isAuthorized(),
                        // If isAuthenticated failed then do
                        // authenticateWithActivity.
                        // Warning!!! After this point all the
                        // upcoming tasks needs to be handled
                        // in upcoming activity!!!
                        authenticateWithActivity())));
    }

    /**
     * Handle Authentication Chain.
     *
     * @param iAuthorizedHandler Authorized handler.
     */

    public void handleAuthChain(IAuthorizedHandler iAuthorizedHandler) {

        // Check authentication first.
        authenticate().subscribe(resultBundle -> {
            if (resultBundle == null) {
                Log.w(TAG, "resultBundle is null, user probably pressed back on login screen");
            }
            // If we got a login screen and login was success
            else if (resultBundle.getBoolean(RESULT_FROM_ACTIVITY)) {
                // Check if we are authorized in upcoming activity context.
                mContentBrowser.getNavigator().runOnUpcomingActivity(() -> isAuthorized().subscribe(bundle -> {
                    // If we were authorized return success.
                    if (resultBundle.getBoolean(RESULT)) {
                        iAuthorizedHandler.onAuthorized(resultBundle);
                    } else {
                        // If we were not authorized return show error.
                        handleErrorBundle(resultBundle);
                    }
                }));
            } else if (resultBundle.getBoolean(RESULT)) {
                // If we were logged in and authorized return success.
                iAuthorizedHandler.onAuthorized(resultBundle);
            } else {
                // If everything failed then show error.
                mContentBrowser.getNavigator().runOnUpcomingActivity(() -> handleErrorBundle(resultBundle));
            }
        }, throwable -> Log.e(TAG, "handleAuthChain failed:", throwable));
    }

    /**
     * Handle on activity result.
     *
     * @param contentBrowser Content Browser.
     * @param activity       Activity.
     * @param requestCode    Request code.
     * @param resultCode     Result code.
     * @param data           Intent.
     */
    public void handleOnActivityResult(ContentBrowser contentBrowser, Activity activity, int requestCode,
            int resultCode, Intent data) {

        Log.d(TAG, "handleOnActivityResult " + requestCode);
        mRxLauncher.activityResult(requestCode, resultCode, data);
    }

    /**
     * Cancel all ongoing requests.
     */
    public void cancelAllRequests() {

        if (mIAuthentication != null) {
            Log.d(TAG, "cancelAllRequests");
            mIAuthentication.cancelAllRequests();
        }
    }

    /**
     * Attempt to logout from account.
     * TODO: Devtech 2447: Utilize logout method implemented above.
     *
     * @param context Context object.
     */
    private void logoutFromAccount(Context context) {

        Log.v(TAG, "logoutFromAccount called.");
        mIAuthentication.logout(context, new IAuthentication.ResponseHandler() {
            @Override
            public void onSuccess(Bundle extras) {

                broadcastAuthenticationStatus(false);
                Log.d(TAG, "Account logout success");
            }

            @Override
            public void onFailure(Bundle extras) {

                Log.e(TAG, "Account logout failure");
            }
        });
    }

    /**
     * Convert auth error to utils error.
     *
     * @param bundle Auth error bundle.
     * @return Error category.
     */
    public static ErrorUtils.ERROR_CATEGORY convertAuthErrorToErrorUtils(Bundle bundle) {

        switch (bundle.getString(AuthenticationConstants.ERROR_CATEGORY)) {
        case AuthenticationConstants.REGISTRATION_ERROR_CATEGORY:
            return ErrorUtils.ERROR_CATEGORY.REGISTRATION_CODE_ERROR;
        case AuthenticationConstants.NETWORK_ERROR_CATEGORY:
            return ErrorUtils.ERROR_CATEGORY.NETWORK_ERROR;
        case AuthenticationConstants.AUTHENTICATION_ERROR_CATEGORY:
            return ErrorUtils.ERROR_CATEGORY.AUTHENTICATION_ERROR;
        case AuthenticationConstants.AUTHORIZATION_ERROR_CATEGORY:
            return ErrorUtils.ERROR_CATEGORY.AUTHORIZATION_ERROR;
        }
        return ErrorUtils.ERROR_CATEGORY.NETWORK_ERROR;
    }

    /**
     * Handle error bundle.
     * TODO: Devtech 2447: Utilize logout method implemented above.
     *
     * @param extras Extras bundle.
     */
    public void handleErrorBundle(Bundle extras) {

        Bundle bundle = extras.getBundle(AuthenticationConstants.ERROR_BUNDLE);
        Activity activity = mContentBrowser.getNavigator().getActiveActivity();
        Log.d(TAG, "handleErrorBundle called" + activity.getLocalClassName());
        ErrorHelper.injectErrorFragment(activity, convertAuthErrorToErrorUtils(bundle),
                (fragment, errorButtonType, errorCategory) -> {
                    if (ErrorUtils.ERROR_BUTTON_TYPE.DISMISS == errorButtonType) {
                        fragment.dismiss();
                    } else if (ErrorUtils.ERROR_BUTTON_TYPE.LOGOUT == errorButtonType) {
                        mIAuthentication.logout(activity, new IAuthentication.ResponseHandler() {
                            @Override
                            public void onSuccess(Bundle extras) {

                                broadcastAuthenticationStatus(false);
                                fragment.dismiss();
                            }

                            @Override
                            public void onFailure(Bundle extras) {

                                fragment.getArguments().putString(ErrorDialogFragment.ARG_ERROR_MESSAGE,
                                        activity.getResources().getString(R.string.logout_failure_message));
                            }
                        });
                    }
                });
    }

    /**
     * Retrieves the list of possible MVPD providers from the URL found at R.string.mvpd_url in
     * custom.xml and gives them to ContentBrowser.
     */
    public void setupMvpdList() {

        try {

            String mvpdUrl = mAppContext.getResources().getString(R.string.mvpd_url);

            // The user has no MVPD URL set up.
            if (mvpdUrl.equals(DEFAULT_MVPD_URL)) {
                Log.d(TAG, "MVPD feature not used.");
                return;
            }
            String jsonStr = NetworkUtils.getDataLocatedAtUrl(mvpdUrl);

            JSONObject json = new JSONObject(jsonStr);
            JSONArray mvpdWhiteList = json.getJSONArray(MVPD_WHITE_LIST);

            for (int i = 0; i < mvpdWhiteList.length(); i++) {

                JSONObject mvpdItem = mvpdWhiteList.getJSONObject(i);
                mContentBrowser.addPoweredByLogoUrlByName(mvpdItem.getString(PreferencesConstants.MVPD_LOGO_URL),
                        mvpdItem.getString(LOGGED_IN_IMAGE));
            }

        } catch (Exception e) {
            Log.e(TAG, "Get MVPD logo urls failed!!!", e);

        }
    }

    /**
     * Load powered by logo from preferences with Glide.
     *
     * @param context                The context to use.
     * @param poweredByLogoImageView The image view to use.
     */
    public void loadPoweredByLogo(Context context, ImageView poweredByLogoImageView) {

        try {
            String mvpdUrl = Preferences.getString(PreferencesConstants.MVPD_LOGO_URL);

            if (poweredByLogoImageView == null || mvpdUrl.isEmpty()) {
                Log.d(TAG, "No MVPD image view or URL found.");
                return;
            }
            poweredByLogoImageView.setVisibility(View.INVISIBLE);

            RequestListener listener = new RequestListener<String, GlideDrawable>() {
                @Override
                public boolean onException(Exception e, String model, Target<GlideDrawable> target,
                        boolean isFirstResource) {

                    poweredByLogoImageView.setVisibility(View.INVISIBLE);
                    Log.e(TAG, "Get image with glide failed for powered by logo!!!", e);
                    return false;
                }

                @Override
                public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target,
                        boolean isFromMemoryCache, boolean isFirstResource) {

                    poweredByLogoImageView.setVisibility(View.VISIBLE);
                    return false;
                }
            };

            GlideHelper.loadImageIntoView(poweredByLogoImageView, context, mvpdUrl, listener,
                    android.R.color.transparent,
                    new ColorDrawable(ContextCompat.getColor(context, android.R.color.transparent)));
        } catch (Exception e) {
            Log.e(TAG, "loadPoweredByLogo failed, activity may not include it.", e);
        }
    }
}