com.mastercard.masterpasswallet.fragments.MainCardFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.mastercard.masterpasswallet.fragments.MainCardFragment.java

Source

/*
 *  ****************************************************************************
 *  Copyright (c) 2015, MasterCard International Incorporated and/or its
 *  affiliates. All rights reserved.
 *  <p/>
 *  The contents of this file may only be used subject to the MasterCard
 *  Mobile Payment SDK for MCBP and/or MasterCard Mobile MPP UI SDK
 *  Materials License.
 *  <p/>
 *  Please refer to the file LICENSE.TXT for full details.
 *  <p/>
 *  TO THE EXTENT PERMITTED BY LAW, 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
 *  NON INFRINGEMENT. TO THE EXTENT PERMITTED BY LAW, IN NO EVENT SHALL
 *  MASTERCARD OR ITS AFFILIATES 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.mastercard.masterpasswallet.fragments;

import android.app.ActionBar;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Handler;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.mastercard.masterpasswallet.Constants;
import com.mastercard.masterpasswallet.DataManager;
import com.mastercard.masterpasswallet.R;
import com.mastercard.masterpasswallet.WalletApplication;
import com.mastercard.masterpasswallet.adapters.CardPagerAdapter;
import com.mastercard.masterpasswallet.database.helpers.TransactionRecordHelper;
import com.mastercard.masterpasswallet.models.Card;
import com.mastercard.masterpasswallet.models.PaymentContext;
import com.mastercard.masterpasswallet.models.TransactionRecord;
import com.mastercard.masterpasswallet.widgets.PagerContainer;
import com.mastercard.masterpasswallet.widgets.ProgressWheel;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Locale;

/**
 * Main fragment that displays list of cards and button for contactless
 */
public class MainCardFragment extends BaseFragment<MainCardFragment.MainCardFragmentListener> {
    private static final String TAG = MainCardFragment.class.getName();

    /**
     * Interface exposed by this fragment.
     */
    public interface MainCardFragmentListener {
        void addCard();

        void showShippingAddresses();

        void showCardInfo(Card card);

        void tapAndPay(Card card);

        void autoCancel();

        boolean isPaymentInProgress();

        void onPaymentInProgress();
    }

    private static final int PAYMENT_IN_PROGRESS_CHECK_TIMEOUT = 100;

    private final Handler mHandler = new Handler();

    private ViewPager mPager;
    private PagerAdapter mAdapter;
    private PagerContainer mPagerContainer;
    private TextView mTxtLastTransactionMerchant;
    private TextView mTxtLastTransactionDate;
    private TextView mTxtLastTransactionAmount;
    private View mContainerNoCards;
    private View mContainerCards;
    private View mOverlay;
    private View mOverlayCard;
    private ImageView mImgOverlayCard;
    private ImageView mInactiveOverlay;
    private TextView mTxtOverlayCardPan;
    private TextView mTxtTapInstruction;
    private ImageView mImgPayInStore;
    private AnimationDrawable mAnimWave;
    private ImageView mImgResultIndicator;
    private ViewpagerIndicatorFragment mViewpagerIndicator;
    private ProgressWheel mProgressWheel;
    private boolean bPaymentInProgress;
    private CountDownTimer mTimeoutTimer;
    public boolean bStartPaymentImmediately;

    private boolean bPaymentActuallyTimedOut = false;

    private ArrayList<Card> mCards;
    private Card mCurrentCard;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);

        mCards = DataManager.INSTANCE.getFreshCards();
        if (mCards != null && mCards.size() > 0) {
            mCurrentCard = DataManager.INSTANCE.getSelectedCard();
            Log.d(TAG, "Set current card to 2: " + mCurrentCard.getDpan());
        }
    }

    @Override
    public void onResume() {
        // Ensure cards are up to date
        mCards.clear();

        ActionBar actionBar = getActivity().getActionBar();
        if (actionBar != null) {
            setActionBarLogo(actionBar, true);
        }

        ArrayList<Card> cards = DataManager.INSTANCE.getFreshCards();
        if (cards != null && cards.size() > 0) {
            mCards.addAll(cards);
        }
        mAdapter.notifyDataSetChanged();

        // scroll to the card just added if there is one
        if (!TextUtils.isEmpty(DataManager.INSTANCE.mLastAddedDigitizedCardId)) {
            int indexOfJustAddedCard = 0;
            for (Card card : mCards) {
                if (card.getDpan().equals(DataManager.INSTANCE.mLastAddedDigitizedCardId)) {
                    mPager.setCurrentItem(indexOfJustAddedCard, false);
                    DataManager.INSTANCE.setSelectedCard(card);
                    break;
                }
                indexOfJustAddedCard++;
            }

            // Regardless of whether we found it or not
            DataManager.INSTANCE.mLastAddedDigitizedCardId = null;
        }

        if (mCurrentCard == null) {
            if (mCards.size() > 0) {
                setCurrentCard(DataManager.INSTANCE.getSelectedCard());
                Log.d(TAG, "Set current card to 3: " + mCurrentCard.getDpan());

                updateUIForCards();
            } else {
                updateUIForNoCards();
            }
        }

        if (mCurrentCard != null) {
            // If we're in payment context, auto-scroll to the default card
            // (Only if there is no other pre-selected card)
            if (DataManager.INSTANCE.getPaymentContext() == PaymentContext.CONTACTLESS) {
                Log.d(TAG, "We're in payment context, go to default card");
                int indexOfDefaultCard = 0;
                for (Card card : mCards) {
                    if (card.isDefault(PaymentContext.CONTACTLESS)) {
                        mPager.setCurrentItem(indexOfDefaultCard, false);
                        break;
                    }
                    indexOfDefaultCard++;
                }
            } else {
                int indexOfSelectedCard = 0;
                for (Card card : mCards) {
                    if (card.getDpan().equals(mCurrentCard.getDpan())) {
                        mPager.setCurrentItem(indexOfSelectedCard, false);
                        break;
                    }
                    indexOfSelectedCard++;
                }
            }
            setCurrentCard(mCurrentCard);
        }

        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();

        mHandler.removeCallbacks(rUpdateLastTransaction);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        menu.clear();
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.menu_cards, menu);
        menu.findItem(R.id.show_shipping_addresses).setVisible(false);

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Only handle menu events when not in transaction mode
        if (!mListener.isPaymentInProgress()) {
            switch (item.getItemId()) {
            case R.id.add_card:
                mListener.addCard();
                break;
            /*case R.id.show_shipping_addresses:
                if (BuildConfig.isEmbedded) {
                    mListener.showShippingAddresses();
                }
                break;*/
            }
        } else {
            Toast.makeText(getActivity(), R.string.drawer_not_during_payment, Toast.LENGTH_SHORT).show();
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    protected int getLayoutResourceId() {
        return R.layout.fragment_card_main;
    }

    @Override
    protected void initWidgets(View fragmentView) {
        setActionBarLogo(getActivity().getActionBar(), true);
        findWidgets(fragmentView);
        setupViewpagerIndicator(0);
        setupCardWidget(fragmentView);
        setupAddCardButton(fragmentView);
        setupTapAndPayButton();

        updateLastTransaction();

        if (bStartPaymentImmediately) {
            bStartPaymentImmediately = false;
            startPayment();
        }

    }

    private void setupViewpagerIndicator(int currentItem) {
        FragmentManager fm = getFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();

        Bundle data = new Bundle();
        data.putInt(ViewpagerIndicatorFragment.EXTRA_COUNT, mCards.size());
        data.putInt(ViewpagerIndicatorFragment.EXTRA_CURRENT_ITEM, currentItem);

        mViewpagerIndicator = new ViewpagerIndicatorFragment();
        mViewpagerIndicator.setArguments(data);

        ft.replace(R.id.lay_indicator_container, mViewpagerIndicator);
        ft.commit();
    }

    private void setupTapAndPayButton() {
        mOverlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // Intentional no-op to stop events falling beneath
            }
        });

        mImgPayInStore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startPayment();
            }
        });
    }

    private void updateLastTransaction() {
        if (mCurrentCard != null) {
            updateUIForCards();
            Log.d(TAG, "updateLastTransaction card: " + mCurrentCard.getDpan());
            ArrayList<TransactionRecord> transactions = WalletApplication.getInstance().mTransactionRecordHelper
                    .getByQuery(TransactionRecordHelper.COL_CARD + "=?", new String[] { mCurrentCard.getDpan() },
                            null, null, TransactionRecordHelper.COL_DATE + " DESC");

            if (transactions.size() > 0) {
                TransactionRecord latestTransaction = transactions.get(0);

                SimpleDateFormat sdf = new SimpleDateFormat(
                        DateFormat.getBestDateTimePattern(Locale.US, "MM/dd/yy"), Locale.US);
                String formattedDate = sdf.format(latestTransaction.mDate);

                mTxtLastTransactionDate.setText(formattedDate);
                mTxtLastTransactionAmount.setVisibility(View.VISIBLE);
                mTxtLastTransactionAmount.setText(latestTransaction.mValue);

                if (!android.text.TextUtils.isEmpty(latestTransaction.mMerchant)) {
                    mTxtLastTransactionMerchant.setVisibility(View.VISIBLE);
                    mTxtLastTransactionMerchant.setText(latestTransaction.mMerchant);
                } else {
                    mTxtLastTransactionMerchant.setVisibility(View.INVISIBLE);
                }
            } else {
                mTxtLastTransactionDate.setText(R.string.text_no_transactions);
                mTxtLastTransactionAmount.setText("");
                mTxtLastTransactionAmount.setVisibility(View.GONE);
                mTxtLastTransactionMerchant.setVisibility(View.GONE);
            }
        } else {
            updateUIForNoCards();
        }
    }

    private void findWidgets(View fragmentView) {
        mContainerNoCards = fragmentView.findViewById(R.id.container_no_cards);
        mContainerCards = fragmentView.findViewById(R.id.container_cards);
        mTxtLastTransactionMerchant = (TextView) fragmentView.findViewById(R.id.txt_last_transaction_merchant);
        mTxtLastTransactionDate = (TextView) fragmentView.findViewById(R.id.txt_last_transaction_date);
        mTxtLastTransactionAmount = (TextView) fragmentView.findViewById(R.id.txt_last_transaction_amount);
        mImgPayInStore = (ImageView) fragmentView.findViewById(R.id.img_pay_in_store);
        mImgPayInStore.setBackgroundResource(R.drawable.wave);
        mAnimWave = (AnimationDrawable) mImgPayInStore.getBackground();
        mOverlay = fragmentView.findViewById(R.id.overlay);
        mOverlayCard = fragmentView.findViewById(R.id.overlay_card);
        mImgOverlayCard = (ImageView) fragmentView.findViewById(R.id.img_card);
        mInactiveOverlay = (ImageView) fragmentView.findViewById(R.id.img_overlay);
        mTxtOverlayCardPan = (TextView) fragmentView.findViewById(R.id.txt_card_pan);
        mTxtTapInstruction = (TextView) fragmentView.findViewById(R.id.txt_tap_instruction);
        mImgResultIndicator = (ImageView) fragmentView.findViewById(R.id.img_result_indicator);
        mProgressWheel = (ProgressWheel) fragmentView.findViewById(R.id.progress_wheel);
    }

    private int mLastPage = 0;

    private void setupCardWidget(View fragmentView) {
        mPagerContainer = (PagerContainer) fragmentView.findViewById(R.id.pager_container);
        mPager = mPagerContainer.getViewPager();

        mAdapter = new CardPagerAdapter(getActivity(), mCards, mCardClickedListener);
        mPager.setAdapter(mAdapter);
        mPager.setOffscreenPageLimit(mAdapter.getCount());
        mPager.setPageMargin(Constants.CARD_PAGER_MARGIN);
        mPager.setClipChildren(false);
        mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // Intentional no-op
            }

            @Override
            public void onPageSelected(int position) {
                Log.d(TAG, "onPageSelected(" + position + ")");
                mLastPage = position;

                setCurrentCard(mCards.get(position));
                Log.d(TAG, "Set current card to 5: " + mCurrentCard.getDpan());

                mViewpagerIndicator.setCurrentItem(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                // Intentional no-op
            }
        });
    }

    private void setCurrentCard(Card card) {
        mCurrentCard = card;
        updateOverlay();
        updateLastTransaction();
    }

    private void updateOverlay() {
        if (mCurrentCard != null) {
            if (mImgOverlayCard == null || mTxtOverlayCardPan == null) {
                Log.d(TAG, "mImgOverlayCard not initialized");
            } else {
                mImgOverlayCard.setImageResource(mCurrentCard.getCardImageFull());
                mTxtOverlayCardPan
                        .setText(getActivity().getString(R.string.masked_pan, " " + mCurrentCard.getPANEnding()));
            }

        }
    }

    private final CardPagerAdapter.CardClickedListener mCardClickedListener = new CardPagerAdapter.CardClickedListener() {
        @Override
        public void showCardInfo(Card card) {
            mListener.showCardInfo(card);
        }

        @Override
        public void makeDefaultcard(Card card) {
            card.setDefaultCard(PaymentContext.CONTACTLESS);

            // Refresh the list so the icons can be updated
            mPager.setAdapter(mAdapter);
            mPager.setCurrentItem(mLastPage, false);
        }
    };

    public void cardDeleted(Card deletedCard) {
        if (mCards.size() == 1) {
            // If it was the only card then refresh completely
            updateUIForNoCards();
            setCurrentCard(null);
        } else {
            // Remove from our list as it is pre-deletion
            mCards.remove(deletedCard);
            // Recreate the adapter with the updated card list
            mAdapter = new CardPagerAdapter(getActivity(), mCards, mCardClickedListener);
            mPager.setAdapter(mAdapter);
            mPager.setCurrentItem(0, false);
            setupViewpagerIndicator(0);
            setCurrentCard(mCards.get(0));
            Log.d(TAG, "Set current card to 1: " + mCurrentCard.getDpan());
            DataManager.INSTANCE.setSelectedCard(mCurrentCard);
        }
    }

    public void refreshCards() {
        mAdapter.notifyDataSetChanged();
        setCurrentCard(mCards.get(mPager.getCurrentItem()));
        Log.d(TAG, "Set current card to 4: " + mCurrentCard.getDpan());
    }

    private void updateUIForCards() {
        mContainerCards.setVisibility(View.VISIBLE);
        mContainerNoCards.setVisibility(View.GONE);

        mAdapter.notifyDataSetChanged();
    }

    private void updateUIForNoCards() {
        mContainerCards.setVisibility(View.GONE);
        mContainerNoCards.setVisibility(View.VISIBLE);
        mTxtLastTransactionDate.setText(R.string.text_no_transactions);
        mTxtLastTransactionAmount.setText("");
        mTxtLastTransactionAmount.setVisibility(View.GONE);
        mTxtLastTransactionMerchant.setVisibility(View.GONE);
    }

    private void setupAddCardButton(View fragmentView) {
        Button btnAddCard = (Button) fragmentView.findViewById(R.id.btn_add_first_card);
        btnAddCard.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mListener.addCard();
            }
        });
    }

    private void hideOverlay() {
        mOverlay.setVisibility(View.GONE);
        mOverlayCard.setVisibility(View.GONE);
        mTxtTapInstruction.setVisibility(View.GONE);

        mAnimWave.stop();
        mImgPayInStore.setBackgroundResource(0);
        mImgPayInStore.setBackgroundResource(R.drawable.wave);
        mAnimWave = (AnimationDrawable) mImgPayInStore.getBackground();
    }

    public void startPayment() {
        // If not active, stop
        if (!mCurrentCard.IsActive()) {
            Toast.makeText(MainCardFragment.this.getActivity(), R.string.main_card_inactive, Toast.LENGTH_SHORT)
                    .show();
            return;
        }
        // Show the overlay + tap instructions
        mOverlay.setVisibility(View.VISIBLE);
        mOverlayCard.setVisibility(View.VISIBLE);
        mTxtTapInstruction.setVisibility(View.VISIBLE);
        mInactiveOverlay.setVisibility(View.INVISIBLE);

        // Start the wave animating
        mAnimWave.start();

        mListener.tapAndPay(mCurrentCard);

        cancelAutoCancel();
        // Wait for the payment to start - the user might need to be authenticated
        mHandler.postDelayed(rWaitForPaymentToStart, PAYMENT_IN_PROGRESS_CHECK_TIMEOUT);
    }

    public void paymentComplete(final long transactionId, String paymentAmount, final String merchantName) {
        bPaymentActuallyTimedOut = false;
        bPaymentCancelled = false;

        cancelAutoCancel();
        hideOverlay();
        updateLastTransaction();

        // Find the transaction and update it. Simulate delayed information about Merchant name
        TransactionRecord transaction = WalletApplication.getInstance().mTransactionRecordHelper.getSingleByQuery(
                TransactionRecordHelper.COL_ID + "=?", new String[] { String.valueOf(transactionId) });

        transaction.mMerchant = merchantName;
        transaction.save();

        mImgResultIndicator.setImageResource(R.drawable.green_tick_bordered);
        mImgResultIndicator.setVisibility(View.VISIBLE);
        mImgPayInStore.setVisibility(View.INVISIBLE);
        hideResultIndicatorAfterDelay();

        // Update the transaction details with the merchant after a short delay
        mHandler.postDelayed(rUpdateLastTransaction, Constants.TRANSACTION_NOTIFICATION_DELAY);
    }

    private final Runnable rUpdateLastTransaction = new Runnable() {
        @Override
        public void run() {
            updateLastTransaction();
        }
    };

    private boolean bPaymentCancelled = false;

    public void paymentCancelled() {
        bPaymentCancelled = true;
    }

    public void paymentFailed() {
        cancelAutoCancel();
        hideOverlay();

        // If the payment timed out we don't need to change the icons around
        if (bPaymentActuallyTimedOut) {

            Toast.makeText(getActivity(), R.string.transaction_timed_out, Toast.LENGTH_SHORT).show();
        } else if (!bPaymentCancelled) {
            // If the payment wasn't cancelled then show an x
            mImgResultIndicator.setImageResource(R.drawable.red_x_bordered);
            mImgResultIndicator.setVisibility(View.VISIBLE);
            mImgPayInStore.setVisibility(View.INVISIBLE);
            hideResultIndicatorAfterDelay();
        }

        bPaymentActuallyTimedOut = false;
        bPaymentCancelled = false;
    }

    private void hideResultIndicatorAfterDelay() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mImgResultIndicator.setVisibility(View.GONE);
                mImgPayInStore.setVisibility(View.VISIBLE);
            }
        }, Constants.PAYMENT_RESULT_INDICATOR_DELAY);
    }

    public void cancelAutoCancel() {
        mHandler.removeCallbacks(rWaitForPaymentToStart);
        mHandler.removeCallbacks(rAutoCancelPayment);
        if (mTimeoutTimer != null) {
            mTimeoutTimer.cancel();
            mProgressWheel.setProgress(360);
            mTimeoutTimer = null;
        }
    }

    private final Runnable rWaitForPaymentToStart = new Runnable() {
        @Override
        public void run() {
            if (mListener != null) {
                if (mListener.isPaymentInProgress()) {
                    Log.d(TAG, "Payment is in progress, start timer");
                    setActionBarLogo(getActivity().getActionBar(), true);
                    mListener.onPaymentInProgress();
                    // Start a timer and it the payment isn't complete or failed within X seconds then
                    // cancel the payment automatically
                    mHandler.postDelayed(rAutoCancelPayment, Constants.CONTACTLESS_TRANSACTION_AUTO_TIMEOUT);
                    startProgressWheelTimer();
                } else {
                    mHandler.postDelayed(rWaitForPaymentToStart, PAYMENT_IN_PROGRESS_CHECK_TIMEOUT);
                }
            }
        }
    };

    private final Runnable rAutoCancelPayment = new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Auto-cancelling payment");

            if (mListener != null) {
                bPaymentActuallyTimedOut = true;
                mListener.autoCancel();
            }
        }
    };

    private void startProgressWheelTimer() {
        // Start a countdown timer so we only stay waiting a short period after a PIN has been
        // entered
        mProgressWheel.setProgress(0);
        mTimeoutTimer = new CountDownTimer(Constants.CONTACTLESS_TRANSACTION_AUTO_TIMEOUT, 100) {

            public void onTick(long millisUntilFinished) {
                // Work out the progress of the progress wheel
                int progress = (int) (((Constants.CONTACTLESS_TRANSACTION_AUTO_TIMEOUT - millisUntilFinished)
                        / (Constants.CONTACTLESS_TRANSACTION_AUTO_TIMEOUT * 1.0f)) * 360);
                mProgressWheel.setProgress(progress);

            }

            public void onFinish() {
                // Make sure the UI is up to date as we don't see the last tick of the timer
                mProgressWheel.setProgress(360);
            }
        }.start();
    }
}