li.barter.activities.AbstractBarterLiActivity.java Source code

Java tutorial

Introduction

Here is the source code for li.barter.activities.AbstractBarterLiActivity.java

Source

/*
 * Copyright (C) 2014 barter.li
 *
 * 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 li.barter.activities;

import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Typeface;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ClearCacheRequest;

import java.util.concurrent.atomic.AtomicInteger;

import de.keyboardsurfer.android.widget.crouton.Crouton;
import li.barter.BarterLiApplication;
import li.barter.R;
import li.barter.analytics.GoogleAnalyticsManager;
import li.barter.chat.ChatService;
import li.barter.data.DBInterface;
import li.barter.data.DBInterface.AsyncDbQueryCallback;
import li.barter.data.TableChatMessages;
import li.barter.data.TableChats;
import li.barter.fragments.AbstractBarterLiFragment;
import li.barter.fragments.FragmentTransition;
import li.barter.http.IBlRequestContract;
import li.barter.http.IVolleyHelper;
import li.barter.http.ResponseInfo;
import li.barter.http.VolleyCallbacks;
import li.barter.http.VolleyCallbacks.IHttpCallbacks;
import li.barter.utils.AppConstants;
import li.barter.utils.AppConstants.DeviceInfo;
import li.barter.utils.AppConstants.Keys;
import li.barter.utils.AppConstants.QueryTokens;
import li.barter.utils.AppConstants.UserInfo;
import li.barter.utils.Logger;
import li.barter.utils.SharedPreferenceHelper;
import li.barter.utils.Utils;
import li.barter.widgets.TypefaceCache;
import li.barter.widgets.TypefacedSpan;
import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
import uk.co.chrisjenx.calligraphy.CalligraphyTypefaceSpan;
import uk.co.chrisjenx.calligraphy.CalligraphyUtils;

/**
 * @author Vinay S Shenoy Base class for inheriting all other Activities from
 */
public abstract class AbstractBarterLiActivity extends ActionBarActivity
        implements IHttpCallbacks, AsyncDbQueryCallback, OnClickListener {

    private static final String TAG = "BaseBarterLiActivity";

    private static final int ACTION_BAR_DISPLAY_MASK = ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE
            | ActionBar.DISPLAY_USE_LOGO | ActionBar.DISPLAY_SHOW_HOME;
    /**
     * {@link VolleyCallbacks} for encapsulating Volley request responses
     */
    protected VolleyCallbacks mVolleyCallbacks;
    private AtomicInteger mRequestCounter;
    private ActivityTransition mActivityTransition;

    /**
     * Whether a screen hit should be reported to analytics
     */
    private boolean mShouldReportScreenHit;

    /**
     * Whether the current layout is a multipane layout or not
     */
    private boolean mMultipaneLayout;

    /**
     * Creates a Crouton View based on the style
     *
     * @param context {@link Context} reference to get the {@link LayoutInflater} reference
     * @param message The message to display
     * @param style   The style of Crouton
     * @return A View to display as a Crouton
     */
    private static View getCroutonViewForStyle(final Context context, final String message,
            final AlertStyle style) {
        int layoutResId = R.layout.crouton_info; //Default layout
        switch (style) {

        case ALERT: {
            layoutResId = R.layout.crouton_alert;
            break;
        }

        case ERROR: {
            layoutResId = R.layout.crouton_error;
            break;
        }

        case INFO: {
            layoutResId = R.layout.crouton_info;
        }
        }
        final View croutonText = LayoutInflater.from(context).inflate(layoutResId, null);
        ((TextView) croutonText.findViewById(R.id.text_message)).setText(message);
        return croutonText;
    }

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
        super.onCreate(savedInstanceState);

        mMultipaneLayout = getResources().getBoolean(R.bool.multipane);

        /* Here, getClass() might show an Ambiguous method call bug. It's a bug in IntelliJ IDEA 13
        * http://youtrack.jetbrains.com/issue/IDEA-72835 */
        mActivityTransition = getClass().getAnnotation(ActivityTransition.class);

        long lastScreenTime = 0l;
        if (savedInstanceState == null) {
            if (mActivityTransition != null) {
                overridePendingTransition(mActivityTransition.createEnterAnimation(),
                        mActivityTransition.createExitAnimation());
            }
        } else {
            lastScreenTime = savedInstanceState.getLong(Keys.LAST_SCREEN_TIME);
        }

        if (Utils.shouldReportScreenHit(lastScreenTime)) {
            mShouldReportScreenHit = true;
        } else {
            mShouldReportScreenHit = false;
        }

        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayOptions(ACTION_BAR_DISPLAY_MASK);
            setActionBarTitle(getTitle().toString());
        }

        final RequestQueue requestQueue = ((IVolleyHelper) getApplication()).getRequestQueue();

        mVolleyCallbacks = new VolleyCallbacks(requestQueue, this);
        mRequestCounter = new AtomicInteger(0);
        setProgressBarIndeterminateVisibility(false);
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(new CalligraphyContextWrapper(newBase));
    }

    /**
     * Whether the current layout is a multipane layout or not
     */
    public boolean isMultipane() {
        return mMultipaneLayout;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putLong(Keys.LAST_SCREEN_TIME, Utils.getCurrentEpochTime());
    }

    @Override
    protected void onResume() {
        super.onResume();
        checkAndReportScreenHit();
    }

    /**
     * Reports a screen hit
     */
    public void checkAndReportScreenHit() {
        if (mShouldReportScreenHit) {
            final String analyticsScreenName = getAnalyticsScreenName();

            if (!TextUtils.isEmpty(analyticsScreenName)) {
                GoogleAnalyticsManager.getInstance().sendScreenHit(getAnalyticsScreenName());
            }
        }

    }

    /**
     * Gets the screen name for reporting to google analytics. Send empty string, or
     * <code>null</code> if you don't want the Activity tracked
     */
    protected abstract String getAnalyticsScreenName();

    /**
     * Disconnects the chat service, clears any local data
     */
    public void logout() {

        if (isLoggedIn()) {

            //TODO: Vinay - Add Dialog about chat messages being lost
            final RequestQueue requestQueue = ((BarterLiApplication) getApplication()).getRequestQueue();
            requestQueue.add(new ClearCacheRequest(requestQueue.getCache(), new Runnable() {
                @Override
                public void run() {
                    UserInfo.INSTANCE.reset();
                    DBInterface.deleteAsync(QueryTokens.DELETE_CHATS, getTaskTag(), null, TableChats.NAME, null,
                            null, true, AbstractBarterLiActivity.this);
                    DBInterface.deleteAsync(QueryTokens.DELETE_CHAT_MESSAGES, getTaskTag(), null,
                            TableChatMessages.NAME, null, null, true, AbstractBarterLiActivity.this);
                    SharedPreferenceHelper.removeKeys(AbstractBarterLiActivity.this, R.string.pref_auth_token,
                            R.string.pref_email, R.string.pref_description, R.string.pref_location,
                            R.string.pref_first_name, R.string.pref_last_name, R.string.pref_user_id,
                            R.string.pref_profile_image, R.string.pref_share_token, R.string.pref_referrer,
                            R.string.pref_referrer_count);
                    final Intent disconnectChatIntent = new Intent(AbstractBarterLiActivity.this,
                            ChatService.class);
                    disconnectChatIntent.setAction(AppConstants.ACTION_DISCONNECT_CHAT);
                    startService(disconnectChatIntent);
                    LocalBroadcastManager.getInstance(BarterLiApplication.getStaticContext())
                            .sendBroadcast(new Intent(AppConstants.ACTION_USER_INFO_UPDATED));
                    final Intent homeIntent = new Intent(AbstractBarterLiActivity.this, HomeActivity.class);
                    homeIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    startActivity(homeIntent);
                }
            }));

        }
    }

    @Override
    public void onInsertComplete(final int token, final Object cookie, final long insertRowId) {

    }

    @Override
    public void onDeleteComplete(final int token, final Object cookie, final int deleteCount) {

        switch (token) {
        case QueryTokens.DELETE_CHAT_MESSAGES: {
            Logger.v(TAG, "Deleted %d messages", deleteCount);
            break;
        }

        case QueryTokens.DELETE_CHATS: {
            Logger.v(TAG, "Deleted %d chats", deleteCount);
            break;
        }

        case QueryTokens.DELETE_MY_BOOKS: {
            Logger.v(TAG, "Deleted %d books", deleteCount);
            break;
        }

        default:
            break;
        }
    }

    @Override
    public void onQueryComplete(final int token, final Object cookie, final Cursor cursor) {

    }

    @Override
    public void onUpdateComplete(final int token, final Object cookie, final int updateCount) {

    }

    /**
     * Add a request on the network queue
     *
     * @param request              The {@link Request} to add
     * @param showErrorOnNoNetwork Whether an error toast should be displayed on no internet
     *                             connection
     * @param errorMsgResId        String resource Id for error message to show if no internet
     *                             connection, 0 for a default error message
     */
    protected void addRequestToQueue(final Request<?> request, final boolean showErrorOnNoNetwork,
            final int errorMsgResId, boolean addHeader) {
        if (isConnectedToInternet()) {
            request.setTag(getTaskTag());
            mVolleyCallbacks.queue(request, addHeader);
        } else if (showErrorOnNoNetwork) {
            showCrouton(errorMsgResId != 0 ? errorMsgResId : R.string.no_network_connection, AlertStyle.ERROR);
        }
    }

    /**
     * A Tag to add to all async requests. This must be unique for all Activity types
     *
     * @return An Object that's the tag for this fragment
     */
    protected abstract Object getTaskTag();

    @Override
    protected void onStop() {
        super.onStop();
        // Cancel all pending requests because they shouldn't be delivered
        mVolleyCallbacks.cancelAll(getTaskTag());
        DBInterface.cancelAll(getTaskTag());
        setProgressBarIndeterminateVisibility(false);
    }

    public void setActionBarDisplayOptions(final int displayOptions) {
        if (getSupportActionBar() != null) {
            getSupportActionBar().setDisplayOptions(displayOptions, ACTION_BAR_DISPLAY_MASK);
        }
    }

    /**
     * Is the device connected to a network or not.
     *
     * @return <code>true</code> if connected, <code>false</code> otherwise
     */
    public boolean isConnectedToInternet() {
        return DeviceInfo.INSTANCE.isNetworkConnected();
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {

        //Fetch the current primary fragment. If that will handle the Menu click,
        // pass it to that one
        final AbstractBarterLiFragment currentMainFragment = (AbstractBarterLiFragment) getSupportFragmentManager()
                .findFragmentById(R.id.frame_content);

        boolean handled = false;
        if (currentMainFragment != null) {
            handled = currentMainFragment.onOptionsItemSelected(item);
        }

        if (!handled) {
            // To provide Up navigation
            if (item.getItemId() == android.R.id.home) {

                doUpNavigation();
                return true;
            } else {
                return super.onOptionsItemSelected(item);
            }

        }

        return handled;

    }

    /**
     * Moves up in the hierarchy using the Support meta data specified in manifest
     */
    private void doUpNavigation() {
        final Intent upIntent = NavUtils.getParentActivityIntent(this);

        if (upIntent == null) {

            NavUtils.navigateUpFromSameTask(this);

        } else {
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // This activity is NOT part of this app's task, so create a
                // new
                // task
                // when navigating up, with a synthesized back stack.
                TaskStackBuilder.create(this)
                        // Add all of this activity's parents to the back stack
                        .addNextIntentWithParentStack(upIntent)
                        // Navigate up to the closest parent
                        .startActivities();
            } else {
                // This activity is part of this app's task, so simply
                // navigate up to the logical parent activity.
                NavUtils.navigateUpTo(this, upIntent);
            }
        }

    }

    /**
     * Sets the Action bar title, using the desired {@link Typeface} loaded from {@link
     * TypefaceCache}
     *
     * @param title The title to set for the Action Bar
     */

    public final void setActionBarTitle(final String title) {

        final SpannableString s = new SpannableString(title);
        s.setSpan(new TypefacedSpan(this, TypefaceCache.SLAB_REGULAR), 0, s.length(),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // Update the action bar title with the TypefaceSpan instance
        final ActionBar actionBar = getSupportActionBar();
        actionBar.setTitle(s);
    }

    /**
     * Sets the Action bar title, using the desired {@link Typeface} loaded from {@link
     * TypefaceCache}
     *
     * @param titleResId The title string resource Id to set for the Action Bar
     */
    public final void setActionBarTitle(final int titleResId) {
        setActionBarTitle(getString(titleResId));
    }

    /**
     * Display an alert, with a string message
     *
     * @param message The message to display
     * @param style   The {@link AlertStyle} of message to display
     */
    public void showCrouton(final String message, final AlertStyle style) {
        //Crouton.make(activity, customView, viewGroupResId, configuration)
        //Crouton.make(this, getCroutonViewForStyle(this, message, style)).show();
        Crouton.make(this, getCroutonViewForStyle(this, message, style))
                .setConfiguration(new de.keyboardsurfer.android.widget.crouton.Configuration.Builder()
                        .setDuration(de.keyboardsurfer.android.widget.crouton.Configuration.DURATION_SHORT).build())
                .show();
    }

    /**
     * Display an alert, with a string message
     *
     * @param messageResId The message to display
     * @param style        The {@link AlertStyle} of message to display
     */
    public void showCrouton(final int messageResId, final AlertStyle style) {
        showCrouton(getString(messageResId), style);
    }

    /**
     * Display an alert, with a string message with infinite time
     *
     * @param message The message to display
     * @param style   The {@link AlertStyle} of message to display
     */
    public void showInfiniteCrouton(final String message, final AlertStyle style) {
        Crouton.make(this, getCroutonViewForStyle(this, message, style))
                .setConfiguration(new de.keyboardsurfer.android.widget.crouton.Configuration.Builder()
                        .setDuration(de.keyboardsurfer.android.widget.crouton.Configuration.DURATION_INFINITE)
                        .build())
                .show();

    }

    /**
     * Display an alert, with a string message with infinite time
     *
     * @param messageResId The message to display
     * @param style        The {@link AlertStyle} of message to display
     */
    public void showInfiniteCrouton(final int messageResId, final AlertStyle style) {
        showInfiniteCrouton(getString(messageResId), style);
    }

    /**
     * Cancels all queued {@link Crouton}s. If there is a {@link Crouton} displayed currently, it
     * will be the last one displayed.
     */
    public void cancelAllCroutons() {
        Crouton.cancelAllCroutons();
    }

    /**
     * Finish the Activity, specifying whether to use custom or default animations
     *
     * @param defaultAnimation <code>true</code> to use Activity default animation,
     *                         <code>false</code> to use custom Animation. In order for the custom
     *                         Animation to be applied, however, you must add the {@link
     *                         ActivityTransition} Annotation to the Activity declaration
     */
    public void finish(final boolean defaultAnimation) {
        super.finish();
        if ((mActivityTransition != null) && !defaultAnimation) {
            overridePendingTransition(mActivityTransition.destroyEnterAnimation(),
                    mActivityTransition.destroyExitAnimation());
        }
    }

    @Override
    public void finish() {
        finish(false);
    }

    /**
     * Helper method to load fragments into layout
     *
     * @param containerResId The container resource Id in the content view into which to load the
     *                       fragment
     * @param fragment       The fragment to load
     * @param tag            The fragment tag
     * @param addToBackStack Whether the transaction should be addded to the backstack
     * @param backStackTag   The tag used for the backstack tag
     */
    public void loadFragment(final int containerResId, final AbstractBarterLiFragment fragment, final String tag,
            final boolean addToBackStack, final String backStackTag) {

        loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag, false);
    }

    /**
     * Helper method to load fragments into layout
     *
     * @param containerResId The container resource Id in the content view into which to load the
     *                       fragment
     * @param fragment       The fragment to load
     * @param tag            The fragment tag
     * @param addToBackStack Whether the transaction should be addded to the backstack
     * @param backStackTag   The tag used for the backstack tag
     * @param customAnimate  Whether to provide a custom animation for the Fragment. If
     *                       <code>true</code>, the Fragment also needs to be annotated with a
     *                       {@linkplain li.barter.fragments.FragmentTransition} annotation which
     *                       describes the transition to perform. If <code>false</code>, will use
     *                       default fragment transition
     */
    public void loadFragment(final int containerResId, final AbstractBarterLiFragment fragment, final String tag,
            final boolean addToBackStack, final String backStackTag, final boolean customAnimate) {

        loadFragment(containerResId, fragment, tag, addToBackStack, backStackTag, customAnimate, false);

    }

    /**
     * Helper method to load fragments into layout
     *
     * @param containerResId The container resource Id in the content view into which to load the
     *                       fragment
     * @param fragment       The fragment to load
     * @param tag            The fragment tag
     * @param addToBackStack Whether the transaction should be addded to the backstack
     * @param backStackTag   The tag used for the backstack tag
     * @param customAnimate  Whether to provide a custom animation for the Fragment. If
     *                       <code>true</code>, the Fragment also needs to be annotated with a
     *                       {@linkplain li.barter.fragments.FragmentTransition} annotation which
     *                       describes the transition to perform. If <code>false</code>, will use
     *                       default fragment transition
     * @param remove         Whether the fragment should be removed before adding it
     */
    public void loadFragment(final int containerResId, final AbstractBarterLiFragment fragment, final String tag,
            final boolean addToBackStack, final String backStackTag, final boolean customAnimate,
            final boolean remove) {

        final FragmentManager fragmentManager = getSupportFragmentManager();

        if (remove) {
            fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
            fragmentManager.beginTransaction().remove(fragment).commit();
            fragmentManager.executePendingTransactions();
        }
        final FragmentTransaction transaction = fragmentManager.beginTransaction();

        if (customAnimate) {
            final FragmentTransition fragmentTransition = fragment.getClass()
                    .getAnnotation(FragmentTransition.class);
            if (fragmentTransition != null) {

                transaction.setCustomAnimations(fragmentTransition.enterAnimation(),
                        fragmentTransition.exitAnimation(), fragmentTransition.popEnterAnimation(),
                        fragmentTransition.popExitAnimation());

            }
        }

        transaction.replace(containerResId, fragment, tag);

        if (addToBackStack) {
            transaction.addToBackStack(backStackTag);
        }
        transaction.commit();
    }

    /**
     * Is the user logged in
     */
    protected boolean isLoggedIn() {
        return !TextUtils.isEmpty(UserInfo.INSTANCE.getAuthToken());
    }

    @Override
    public void onWindowFocusChanged(final boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        //Reset background to reduce overdaw
        getWindow().setBackgroundDrawable(null);
    }

    /**
     * Returns the current master fragment. In single pane layout, this is the fragment in the main
     * content. In a multi-pane layout, returns the fragment in the master container, which is the
     * one responsible for coordination
     *
     * @return <code>null</code> If no fragment is loaded,the {@link AbstractBarterLiFragment}
     * implementation which is the current master fragment otherwise
     */
    public AbstractBarterLiFragment getCurrentMasterFragment() {

        return (AbstractBarterLiFragment) getSupportFragmentManager().findFragmentById(R.id.frame_content);

    }

    @Override
    public void onPreExecute(final IBlRequestContract request) {
        mRequestCounter.incrementAndGet();
        setProgressBarIndeterminateVisibility(true);
    }

    @Override
    public void onPostExecute(final IBlRequestContract request) {
        if (mRequestCounter.decrementAndGet() == 0) {
            setProgressBarIndeterminateVisibility(false);
        }
    }

    @Override
    public abstract void onSuccess(int requestId, IBlRequestContract request, ResponseInfo response);

    @Override
    public abstract void onBadRequestError(int requestId, IBlRequestContract request, int errorCode,
            String errorMessage, Bundle errorResponseBundle);

    @Override
    public void onAuthError(final int requestId, final IBlRequestContract request) {
        //TODO Show Login Fragment and ask user to login again
    }

    @Override
    public void onOtherError(final int requestId, final IBlRequestContract request, final int errorCode) {
        //TODO Show generic network error message
    }

    @Override
    public void onClick(final DialogInterface dialog, final int which) {

        final AbstractBarterLiFragment fragment = getCurrentMasterFragment();

        if ((fragment != null) && fragment.isVisible()) {
            if (fragment.willHandleDialog(dialog)) {
                fragment.onDialogClick(dialog, which);
            }
        }
    }

    @Override
    public void onBackPressed() {

        /* Get the reference to the current master fragment and check if that will handle
        onBackPressed. If yes, do nothing. Else, let the Activity handle it. */
        final AbstractBarterLiFragment masterFragment = getCurrentMasterFragment();

        boolean handled = false;
        if (masterFragment != null && masterFragment.isResumed()) {
            handled = masterFragment.onBackPressed();
        }

        if (!handled) {
            super.onBackPressed();
        }
    }

    /**
     * @author Vinay S Shenoy Enum to handle the different types of Alerts that can be shown
     */

    public enum AlertStyle {
        ALERT, INFO, ERROR
    }
}