com.rnd.snapsplit.view.OwedFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.rnd.snapsplit.view.OwedFragment.java

Source

package com.rnd.snapsplit.view;

/**
 * Copyright Google Inc. All Rights Reserved.
 * <p/>
 * 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
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * 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.
 */

import android.app.Activity;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.v4.app.Fragment;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.security.keystore.KeyProperties;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.Toolbar;
import android.util.Base64;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import com.firebase.ui.database.FirebaseRecyclerAdapter;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.rnd.snapsplit.CitiAPIBase;
import com.rnd.snapsplit.DialogClickListener;
import com.rnd.snapsplit.Friend;
import com.rnd.snapsplit.History;
import com.rnd.snapsplit.PaymentRequest;
import com.rnd.snapsplit.Profile;
import com.rnd.snapsplit.R;
import com.rnd.snapsplit.StorageManager;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

import de.hdodenhof.circleimageview.CircleImageView;

/**
 * Created by Damian on 9/6/2017.
 */

public class OwedFragment extends Fragment
        implements GoogleApiClient.OnConnectionFailedListener, DialogClickListener {

    private static final String TAG = "OwedFragment";
    private DatabaseReference mFirebaseDatabaseReference;
    private FirebaseRecyclerAdapter<PaymentRequest, MessageViewHolder> mFirebaseAdapter;
    private LinearLayoutManager mLinearLayoutManager;
    private RecyclerView mMessageRecyclerView;
    private View view;
    private ProgressBar mProgressBar;
    private Fragment mFragment;
    private Activity activity;
    private Profile profile;

    // fingerprint vars

    private static final String DIALOG_FRAGMENT_TAG = "myFragment";
    private static final String SECRET_MESSAGE = "Very secret message";
    private static final String KEY_NAME_NOT_INVALIDATED = "key_not_invalidated";
    static final String DEFAULT_KEY_NAME = "default_key";
    private Cipher mCipher;

    private KeyStore mKeyStore;
    private KeyGenerator mKeyGenerator;
    private SharedPreferences mSharedPreferences;

    public static OwedFragment newInstance() {
        return new OwedFragment();
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        mFragment = (Fragment) this;
        final Resources resources = context.getResources();
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }

    @Override
    public void onFingerprintNotRecognized() {

    }

    public static class MessageViewHolder extends RecyclerView.ViewHolder {
        View item;
        TextView description;
        TextView from;
        TextView share;
        TextView splitAmount;
        TextView date;
        CircleImageView receiptIcon;
        String id;
        PaymentRequest pr;

        public MessageViewHolder(View v) {
            super(v);
            item = itemView;
            description = (TextView) itemView.findViewById(R.id.description);
            from = (TextView) itemView.findViewById(R.id.txt_person);
            share = (TextView) itemView.findViewById(R.id.txt_share);
            splitAmount = (TextView) itemView.findViewById(R.id.splitAmount);
            date = (TextView) itemView.findViewById(R.id.date);
            receiptIcon = (CircleImageView) itemView.findViewById(R.id.receiptIcon);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mClickListener.onItemClick(v, getAdapterPosition(), id, pr);
                }
            });

            itemView.setOnLongClickListener(new View.OnLongClickListener() {

                @Override
                public boolean onLongClick(View v) {
                    mLongClickListener.onLongClick(v, getAdapterPosition(), id, pr);
                    return true;
                }
            });
        }

        private MessageViewHolder.ClickListener mClickListener;
        private MessageViewHolder.LongClickListener mLongClickListener;

        //Interface to send callbacks...
        public interface ClickListener {
            public void onItemClick(View view, int position, String id, PaymentRequest pr);
            //public void onLongItemClick(View view, int position, String id, PaymentRequest pr);
        }

        public interface LongClickListener {
            //public void onItemClick(View view, int position, String id, PaymentRequest pr);
            public void onLongClick(View view, int position, String id, PaymentRequest pr);
        }

        public void setOnClickListener(MessageViewHolder.ClickListener clickListener) {
            mClickListener = clickListener;
        }

        public void setOnLongClickListener(MessageViewHolder.LongClickListener clickListener) {
            mLongClickListener = clickListener;
        }

    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //super.onCreate(savedInstanceState);
        view = inflater.inflate(R.layout.activity_owed, container, false);
        activity = getActivity();
        profile = new Profile(getContext());
        ((Toolbar) getActivity().findViewById(R.id.tool_bar_hamburger)).setVisibility(View.VISIBLE);

        mProgressBar = (ProgressBar) view.findViewById(R.id.progressBar);
        mMessageRecyclerView = (RecyclerView) view.findViewById(R.id.messageRecyclerView);
        mLinearLayoutManager = new LinearLayoutManager(getContext());
        //mLinearLayoutManager.setStackFromEnd(true);
        mFirebaseDatabaseReference = FirebaseDatabase.getInstance().getReference().child("requests");
        mFirebaseAdapter = new FirebaseRecyclerAdapter<PaymentRequest, MessageViewHolder>(PaymentRequest.class,
                R.layout.list_owed, MessageViewHolder.class,
                mFirebaseDatabaseReference.orderByChild("requestEpochDate")) {

            @Override
            protected PaymentRequest parseSnapshot(DataSnapshot snapshot) {
                PaymentRequest pr = super.parseSnapshot(snapshot);
                if (pr != null) {
                    pr.setId(snapshot.getKey());
                    return pr;
                }
                return null;
            }

            @Override
            protected void populateViewHolder(final MessageViewHolder viewHolder, PaymentRequest pr, int position) {
                mProgressBar.setVisibility(ProgressBar.INVISIBLE);
                if (pr != null && pr.getReceipientPhoneNo().equals(profile.getPhoneNumber())) {

                    if (pr.getStrReceiptPic() != null && !pr.getStrReceiptPic().equals("")) {
                        String encodedReceipt = pr.getStrReceiptPic();
                        byte[] encodeByte = Base64.decode(encodedReceipt, Base64.DEFAULT);
                        Bitmap bitmap = BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
                        viewHolder.receiptIcon.setImageBitmap(bitmap);
                    }
                    viewHolder.pr = pr;
                    viewHolder.id = pr.getId();
                    viewHolder.description.setText(pr.getDescription());
                    viewHolder.from.setText(
                            "Request sent by: " + pr.getRequestorName() + " - " + pr.getRequestorPhoneNumber());
                    viewHolder.share.setText("Your Share: HKD" + String.format("%.2f", pr.getShareAmount()));
                    viewHolder.splitAmount
                            .setText("Total Amount: HKD" + String.format("%.2f", pr.getTotalAmount()));
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd/MM/yyyy' 'HH:mm:ss");
                    String date = null;
                    Date temp = new Date(Long.parseLong(pr.getRequestEpochDate()) * (-1));
                    date = simpleDateFormat.format(temp);
                    viewHolder.date.setText(date);
                } else {
                    ViewGroup.LayoutParams params = viewHolder.item.getLayoutParams();
                    params.height = 0;
                    viewHolder.item.setLayoutParams(params);
                }

                // log a view action on it
                //FirebaseUserActions.getInstance().end(getMessageViewAction(fd));
            }

            @Override
            public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                MessageViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
                viewHolder.setOnLongClickListener(new MessageViewHolder.LongClickListener() {
                    @Override
                    public void onLongClick(View view, int position, String id, PaymentRequest pr) {
                        AlertDialog.Builder ImageDialog = new AlertDialog.Builder(getActivity());
                        ImageDialog.setTitle("Receipt Preview - " + pr.getDescription());
                        ImageView showImage = new ImageView(getActivity());
                        Bitmap bitmap = null;
                        if (pr.getStrReceiptPic() != null && !pr.getStrReceiptPic().equals("")) {
                            String encodedReceipt = pr.getStrReceiptPic();
                            byte[] encodeByte = Base64.decode(encodedReceipt, Base64.DEFAULT);
                            bitmap = BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.length);
                        }
                        if (bitmap != null) {
                            showImage.setImageBitmap(bitmap);
                        }
                        ImageDialog.setView(showImage);

                        ImageDialog.setNegativeButton("Close Preview", new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface arg0, int arg1) {
                            }
                        });
                        ImageDialog.show();
                    }
                });
                viewHolder.setOnClickListener(new MessageViewHolder.ClickListener() {
                    @Override
                    public void onItemClick(View view, int position, String id, PaymentRequest pr) {
                        //Toast.makeText(getActivity(), "Item clicked at " + position, Toast.LENGTH_SHORT).show();
                        Bundle bundle = new Bundle();
                        bundle.putSerializable("pr", pr);

                        if (initCipher(mCipher, DEFAULT_KEY_NAME)) {
                            // Show the fingerprint dialog. The user has the option to use the fingerprint with
                            // crypto, or you can fall back to using a server-side verified password.
                            DialogFragmentFingerprintAuthentication fragment = new DialogFragmentFingerprintAuthentication();
                            fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
                            boolean useFingerprintPreference = mSharedPreferences
                                    .getBoolean(getString(R.string.use_fingerprint_to_authenticate_key), true);
                            if (useFingerprintPreference) {
                                fragment.setStage(DialogFragmentFingerprintAuthentication.Stage.FINGERPRINT);
                            } else {
                                fragment.setStage(DialogFragmentFingerprintAuthentication.Stage.PASSWORD);
                            }
                            fragment.setArguments(bundle);
                            fragment.setTargetFragment(mFragment, 0);
                            fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
                        } else {
                            // This happens if the lock screen has been disabled or or a fingerprint got
                            // enrolled. Thus show the dialog to authenticate with their password first
                            // and ask the user if they want to authenticate with fingerprints in the
                            // future
                            DialogFragmentFingerprintAuthentication fragment = new DialogFragmentFingerprintAuthentication();
                            fragment.setCryptoObject(new FingerprintManager.CryptoObject(mCipher));
                            fragment.setStage(
                                    DialogFragmentFingerprintAuthentication.Stage.NEW_FINGERPRINT_ENROLLED);
                            fragment.setArguments(bundle);
                            fragment.setTargetFragment(mFragment, 0);
                            fragment.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
                        }
                    }

                });
                return viewHolder;
            }

        };

        mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onItemRangeInserted(int positionStart, int itemCount) {
                super.onItemRangeInserted(positionStart, itemCount);
                int friendlyMessageCount = mFirebaseAdapter.getItemCount();
                int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
                // If the recycler view is initially being loaded or the user is at the bottom of the list, scroll
                // to the bottom of the list to show the newly added message.
                if (lastVisiblePosition == -1 || (positionStart >= (friendlyMessageCount - 1)
                        && lastVisiblePosition == (positionStart - 1))) {
                    mMessageRecyclerView.scrollToPosition(positionStart);
                }
            }
        });

        mMessageRecyclerView.setLayoutManager(mLinearLayoutManager);
        mMessageRecyclerView.setAdapter(mFirebaseAdapter);

        try {
            mKeyStore = KeyStore.getInstance("AndroidKeyStore");
        } catch (KeyStoreException e) {
            throw new RuntimeException("Failed to get an instance of KeyStore", e);
        }
        try {
            mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
        }
        //Cipher defaultCipher;
        Cipher cipherNotInvalidated;
        try {
            mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
                    + KeyProperties.ENCRYPTION_PADDING_PKCS7);
            cipherNotInvalidated = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("Failed to get an instance of Cipher", e);
        }
        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());

        KeyguardManager keyguardManager = getActivity().getSystemService(KeyguardManager.class);
        FingerprintManager fingerprintManager = getActivity().getSystemService(FingerprintManager.class);

        if (!keyguardManager.isKeyguardSecure()) {
            // Show a message that the user hasn't set up a fingerprint or lock screen.
            Toast.makeText(getActivity(),
                    "Secure lock screen hasn't set up.\n"
                            + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint",
                    Toast.LENGTH_LONG).show();
            //return;
        }

        // Now the protection level of USE_FINGERPRINT permission is normal instead of dangerous.
        // See http://developer.android.com/reference/android/Manifest.permission.html#USE_FINGERPRINT
        // The line below prevents the false positive inspection from Android Studio
        // noinspection ResourceType
        if (!fingerprintManager.hasEnrolledFingerprints()) {
            // This happens when no fingerprints are registered.
            Toast.makeText(getActivity(),
                    "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint",
                    Toast.LENGTH_LONG).show();
            //return;
        }

        createKey(DEFAULT_KEY_NAME, true);
        createKey(KEY_NAME_NOT_INVALIDATED, false);

        return view;

    }

    private boolean initCipher(Cipher cipher, String keyName) {
        try {
            mKeyStore.load(null);
            SecretKey key = (SecretKey) mKeyStore.getKey(keyName, null);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            return true;
        } catch (KeyPermanentlyInvalidatedException e) {
            return false;
        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
                | NoSuchAlgorithmException | InvalidKeyException e) {
            throw new RuntimeException("Failed to init Cipher", e);
        }
    }

    public void onPurchased(boolean withFingerprint, @Nullable FingerprintManager.CryptoObject cryptoObject,
            final PaymentRequest pr) {
        if (withFingerprint) {
            // If the user has authenticated with fingerprint, verify that using cryptography and
            // then show the confirmation message.
            assert cryptoObject != null;
            tryEncrypt(cryptoObject.getCipher());
        } else {
            // Authentication happened with backup password. Just show the confirmation message.
            //showConfirmation(null);
        }
        // Citi API doesn't work as the five fake accounts all belong to the same person
        // cannot see the updated balance on the other person and also the transaction amount is somehow wrong
        //                (new CitiAPIBase(getContext())).API_MoneyMovement_CreateInternalTransfer(
        //                        "355a515030616a53576b6a65797359506a634175764a734a3238314e4668627349486a676f7449463949453d"
        //                        , Float.toString(pr.getShareAmount()), "SOURCE_ACCOUNT_CURRENCY"
        //                        , "7977557255484c7345546c4e53424766634b6c53756841672b556857626e395253334b70416449676b42673d"
        //                        , "BENEFICIARY", "123456", pr.getDescription(), "MEDICAL_SERVICES", this, pr);
        //
        Toast.makeText(getActivity(), "Payment to " + pr.getRequestorName() + " successful!", Toast.LENGTH_LONG)
                .show();

        // set account balance, "I" paid here so I am the receipt of the request
        FirebaseDatabase.getInstance().getReference().child(pr.getRequestorPhoneNumber())
                .addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot snapshot) {
                        String updatedRequestorBalance = Float.toString(Float.valueOf(
                                (String) ((HashMap<String, String>) snapshot.getValue()).get("accountBalance"))
                                + pr.getShareAmount());
                        FirebaseDatabase.getInstance().getReference().child(pr.getRequestorPhoneNumber())
                                .child("accountBalance").setValue(updatedRequestorBalance);
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {
                    }
                });
        FirebaseDatabase.getInstance().getReference().child(pr.getReceipientPhoneNo())
                .addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(DataSnapshot snapshot) {
                        String updatedRecipientBalance = Float.toString(Float.valueOf(
                                (String) ((HashMap<String, String>) snapshot.getValue()).get("accountBalance"))
                                - pr.getShareAmount());
                        FirebaseDatabase.getInstance().getReference().child(pr.getReceipientPhoneNo())
                                .child("accountBalance").setValue(updatedRecipientBalance);
                    }

                    @Override
                    public void onCancelled(DatabaseError databaseError) {
                    }
                });

        // remove the request so that it becomes paid
        mFirebaseDatabaseReference.child(pr.getId()).removeValue();

        // set history
        (new History(getContext())).setPaymentHistory(pr);
        (new History(getContext())).setFriendReceivePaymentHistory(pr);
    }

    private void tryEncrypt(Cipher cipher) {
        try {
            byte[] encrypted = cipher.doFinal(SECRET_MESSAGE.getBytes());
            //showConfirmation(encrypted);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            Toast.makeText(getActivity(),
                    "Failed to encrypt the data with the generated key. " + "Retry the purchase", Toast.LENGTH_LONG)
                    .show();
            Log.e(TAG, "Failed to encrypt the data with the generated key." + e.getMessage());
        }
    }

    public void createKey(String keyName, boolean invalidatedByBiometricEnrollment) {
        // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
        // for your flow. Use of keys is necessary if you need to know if the set of
        // enrolled fingerprints has changed.
        try {
            mKeyStore.load(null);
            // Set the alias of the entry in Android KeyStore where the key will appear
            // and the constrains (purposes) in the constructor of the Builder

            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                            .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                            // Require the user to authenticate with a fingerprint to authorize every use
                            // of the key
                            .setUserAuthenticationRequired(true)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);

            // This is a workaround to avoid crashes on devices whose API level is < 24
            // because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
            // visible on API level +24.
            // Ideally there should be a compat library for KeyGenParameterSpec.Builder but
            // which isn't available yet.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);
            }
            mKeyGenerator.init(builder.build());
            mKeyGenerator.generateKey();
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertificateException
                | IOException e) {
            throw new RuntimeException(e);
        }
    }

}