Java tutorial
/* * **************************************************************************** * 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(); } }