com.apptentive.android.sdk.module.messagecenter.view.MessageCenterActivityContent.java Source code

Java tutorial

Introduction

Here is the source code for com.apptentive.android.sdk.module.messagecenter.view.MessageCenterActivityContent.java

Source

/*
 * Copyright (c) 2015, Apptentive, Inc. All Rights Reserved.
 * Please refer to the LICENSE file for the terms and conditions
 * under which redistribution and use of this file is permitted.
 */

package com.apptentive.android.sdk.module.messagecenter.view;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.support.v4.app.DialogFragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.text.Editable;
import android.text.Layout;
import android.text.TextUtils;
import android.view.View;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.TextView;

import com.apptentive.android.sdk.Apptentive;
import com.apptentive.android.sdk.ApptentiveInternal;
import com.apptentive.android.sdk.ApptentiveInternalActivity;
import com.apptentive.android.sdk.Log;
import com.apptentive.android.sdk.R;
import com.apptentive.android.sdk.comm.ApptentiveHttpResponse;

import com.apptentive.android.sdk.module.engagement.EngagementModule;
import com.apptentive.android.sdk.module.engagement.interaction.model.MessageCenterInteraction;
import com.apptentive.android.sdk.module.engagement.interaction.view.InteractionView;
import com.apptentive.android.sdk.module.messagecenter.MessageManager;
import com.apptentive.android.sdk.module.messagecenter.MessagePollingWorker;
import com.apptentive.android.sdk.module.messagecenter.model.ApptentiveMessage;
import com.apptentive.android.sdk.module.messagecenter.model.CompoundMessage;
import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterComposingItem;
import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterStatus;
import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil;
import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterUtil.MessageCenterListItem;
import com.apptentive.android.sdk.module.metric.MetricModule;
import com.apptentive.android.sdk.util.AnimationUtil;
import com.apptentive.android.sdk.util.Constants;
import com.apptentive.android.sdk.util.Util;
import com.apptentive.android.sdk.util.WeakReferenceHandler;
import com.apptentive.android.sdk.util.image.ApptentiveAttachmentLoader;
import com.apptentive.android.sdk.util.image.ImageGridViewAdapter;
import com.apptentive.android.sdk.util.image.ImageItem;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

/**
 * @author Barry Li
 */

public class MessageCenterActivityContent extends InteractionView<MessageCenterInteraction>
        implements MessageManager.AfterSendMessageListener, MessageAdapter.OnListviewItemActionListener,
        MessageManager.OnNewIncomingMessagesListener, AbsListView.OnScrollListener,
        MessageCenterListView.OnListviewResizeListener, ImageGridViewAdapter.Callback {

    // keys used to save instance in the event of rotation
    private final static String LIST_TOP_INDEX = "list_top_index";
    private final static String LIST_TOP_OFFSET = "list_top_offset";
    private final static String COMPOSING_EDITTEXT_STATE = "edittext";
    private final static String COMPOSING_ATTACHMENTS = "attachments";
    private final static String WHO_CARD_MODE = "whocardmode";
    private final static String WHO_CARD_NAME = "whocardname";
    private final static String WHO_CARD_EMAIL = "whocardemail";
    private final static String WHO_CARD_AVATAR_FILE = "whocardavatar";

    private final static int WHO_CARD_MODE_INIT = 1;
    private final static int WHO_CARD_MODE_EDIT = 2;

    private final static long DEFAULT_DELAYMILLIS = 200;

    private Activity viewActivity;
    private View messageCenterHeader;
    private View headerDivider;
    private ListView messageCenterListView; // List of apptentive messages
    private EditText messageEditText; // Composing area
    private View fab;
    private View profileButton;

    // Data backing of the listview
    private ArrayList<MessageCenterListItem> messages = new ArrayList<MessageCenterListItem>();
    private MessageAdapter<MessageCenterListItem> messageCenterListAdapter;

    // MesssageCenterView is set to paused when it fails to send message
    private boolean isPaused = false;
    // Count how many paused ongoing messages
    private int unsendMessagesCount = 0;

    private MessageCenterStatus statusItem;
    private MessageCenterComposingItem composingItem;
    private MessageCenterComposingItem actionBarItem;
    private MessageCenterComposingItem whoCardItem;
    private CompoundMessage contextualMessage;

    private ArrayList<ImageItem> imageAttachmentstList = new ArrayList<ImageItem>();

    /**
     * Used to save the state of the message text box if the user closes Message Center for a moment,
     * , rotate device, attaches a file, etc.
     */
    private Parcelable composingViewSavedState;
    private ArrayList<ImageItem> savedAttachmentstList;

    /*
     * Set to true when user launches image picker, and set to false once an image is picked
     * This is used to track if the user tried to attach an image but abandoned the image picker
     * without picking anything
     */
    private boolean imagePickerLaunched = false;

    /**
     * Used to save the state of the who card if the user closes Message Center for a moment,
     * , rotate device, attaches a file, etc.
     */
    private int pendingWhoCardMode;
    private Parcelable pendingWhoCardName;
    private Parcelable pendingWhoCardEmail;
    private String pendingWhoCardAvatarFile;

    private int listViewSavedTopIndex = -1;
    private int listViewSavedTopOffset;

    protected static final int MSG_SCROLL_TO_BOTTOM = 1;
    protected static final int MSG_SCROLL_FROM_TOP = 2;
    protected static final int MSG_MESSAGE_SENT = 3;
    protected static final int MSG_START_SENDING = 4;
    protected static final int MSG_PAUSE_SENDING = 5;
    protected static final int MSG_RESUME_SENDING = 6;
    protected static final int MSG_MESSAGE_ADD_INCOMING = 7;
    protected static final int MSG_MESSAGE_NOTIFY_UPDATE = 8;

    private final Handler.Callback messageCenterViewCallback = new Handler.Callback() {

        public boolean handleMessage(Message msg) {

            switch (msg.what) {
            case MSG_MESSAGE_NOTIFY_UPDATE: {
                messageCenterListAdapter.notifyDataSetChanged();
                break;
            }
            case MSG_SCROLL_TO_BOTTOM: {
                messageCenterListAdapter.notifyDataSetChanged();
                messageCenterListView.setSelection(messages.size() - 1);
                break;
            }
            case MSG_SCROLL_FROM_TOP: {
                int index = msg.arg1;
                int top = msg.arg2;
                messageCenterListAdapter.notifyDataSetChanged();
                messageCenterListView.setSelectionFromTop(index, top);
                break;
            }
            case MSG_MESSAGE_SENT: {
                unsendMessagesCount--;
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) msg.obj;
                for (MessageCenterListItem message : messages) {
                    if (message instanceof ApptentiveMessage) {
                        String nonce = ((ApptentiveMessage) message).getNonce();
                        if (nonce != null) {
                            String sentNonce = apptentiveMessage.getNonce();
                            if (sentNonce != null && nonce.equals(sentNonce)) {
                                ((ApptentiveMessage) message).setCreatedAt(apptentiveMessage.getCreatedAt());
                                break;
                            }
                        }
                    }
                }
                updateMessageSentStates();
                addExpectationStatusIfNeeded();

                // Update the sent message, make sure it stays in view
                int firstIndex = messageCenterListView.getFirstVisiblePosition();
                View v = messageCenterListView.getChildAt(0);
                int top = (v == null) ? 0 : v.getTop();
                updateMessageSentStates();
                messageCenterViewHandler
                        .sendMessage(messageCenterViewHandler.obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top));
                break;
            }
            case MSG_START_SENDING: {
                Bundle b = msg.getData();
                CompoundMessage message = new CompoundMessage();
                message.setBody(b.getString(COMPOSING_EDITTEXT_STATE));
                message.setRead(true);
                message.setCustomData(ApptentiveInternal.getAndClearCustomData());
                ArrayList<ImageItem> imagesToAttach = b.getParcelableArrayList(COMPOSING_ATTACHMENTS);
                message.setAssociatedImages(viewActivity, imagesToAttach);
                MessageManager.sendMessage(viewActivity.getApplicationContext(), message);
                // Add new outgoing message with animation
                addNewOutGoingMessageItem(message);
                messageCenterListAdapter.notifyDataSetChanged();
                // After the message is sent, check if Who Card need to be shown for the 1st time
                SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME,
                        Context.MODE_PRIVATE);
                boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, false);
                if (!bWhoCardSet) {
                    JSONObject data = new JSONObject();
                    try {
                        data.put("required", interaction.getWhoCardRequired());
                        data.put("trigger", "automatic");
                    } catch (JSONException e) {
                        //
                    }
                    EngagementModule.engageInternal(viewActivity, interaction,
                            MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString());
                    addWhoCard(WHO_CARD_MODE_INIT);
                    messageCenterListAdapter.setForceShowKeyboard(true);
                    // The delay is to ensure the animation of adding Who Card play after the animation of new outgoing message
                    messageCenterViewHandler.sendEmptyMessageDelayed(MSG_MESSAGE_NOTIFY_UPDATE,
                            DEFAULT_DELAYMILLIS);
                }
                break;
            }
            case MSG_PAUSE_SENDING: {
                if (!isPaused) {
                    isPaused = true;
                    if (unsendMessagesCount > 0) {
                        messageCenterListAdapter.setPaused(isPaused);
                        int reason = msg.arg1;
                        if (reason == MessageManager.SEND_PAUSE_REASON_NETWORK) {
                            EngagementModule.engageInternal(viewActivity, interaction,
                                    MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR);
                            MessageCenterStatus newItem = interaction.getErrorStatusNetwork();
                            addNewStatusItem(newItem);
                        } else if (reason == MessageManager.SEND_PAUSE_REASON_SERVER) {
                            EngagementModule.engageInternal(viewActivity, interaction,
                                    MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR);
                            MessageCenterStatus newItem = interaction.getErrorStatusServer();
                            addNewStatusItem(newItem);
                        }
                        messageCenterListAdapter.notifyDataSetChanged();
                    }
                }
                break;
            }
            case MSG_RESUME_SENDING: {
                if (isPaused) {
                    isPaused = false;
                    if (unsendMessagesCount > 0) {
                        clearStatus();
                    }

                    messageCenterListAdapter.setPaused(isPaused);
                    messageCenterListAdapter.notifyDataSetChanged();
                }
                break;
            }
            case MSG_MESSAGE_ADD_INCOMING: {
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) msg.obj;
                displayNewIncomingMessageItem(apptentiveMessage);
                break;
            }
            default:
                return false;
            }
            return true;
        }
    };

    final Handler messageCenterViewHandler = WeakReferenceHandler.create(messageCenterViewCallback);

    public MessageCenterActivityContent(MessageCenterInteraction interaction) {
        super(interaction);
    }

    @Override
    public void doOnCreate(Activity activity, Bundle onSavedInstanceState) {
        activity.setContentView(R.layout.apptentive_message_center);
        viewActivity = activity;

        listViewSavedTopIndex = (onSavedInstanceState == null) ? -1 : onSavedInstanceState.getInt(LIST_TOP_INDEX);
        listViewSavedTopOffset = (onSavedInstanceState == null) ? 0 : onSavedInstanceState.getInt(LIST_TOP_OFFSET);
        composingViewSavedState = (onSavedInstanceState == null) ? null
                : onSavedInstanceState.getParcelable(COMPOSING_EDITTEXT_STATE);
        pendingWhoCardName = (onSavedInstanceState == null) ? null
                : onSavedInstanceState.getParcelable(WHO_CARD_NAME);
        pendingWhoCardEmail = (onSavedInstanceState == null) ? null
                : onSavedInstanceState.getParcelable(WHO_CARD_EMAIL);
        pendingWhoCardAvatarFile = (onSavedInstanceState == null) ? null
                : onSavedInstanceState.getString(WHO_CARD_AVATAR_FILE);
        pendingWhoCardMode = (onSavedInstanceState == null) ? 0 : onSavedInstanceState.getInt(WHO_CARD_MODE);
        String contextualMessageBody = interaction.getContextualMessageBody();
        contextualMessage = CompoundMessage.createAutoMessage(null, contextualMessageBody);

        setup();

        // This listener will run when messages are retrieved from the server, and will start a new thread to update the view.
        MessageManager.addInternalOnMessagesUpdatedListener(this);
        // Give the MessageCenterView a callback when a message is sent.
        MessageManager.setAfterSendMessageListener(this);
        // Needed to prevent the window from being pushed up when a text input area is focused.
        activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        if (listViewSavedTopIndex == -1) {
            messageCenterViewHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS);
        }
    }

    protected void setup() {

        ImageButton closeButton = (ImageButton) viewActivity.findViewById(R.id.close_mc);
        closeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cleanup();
                EngagementModule.engageInternal(viewActivity, interaction,
                        MessageCenterInteraction.EVENT_NAME_CLOSE);
                viewActivity.finish();
            }
        });

        TextView titleTextView = (TextView) viewActivity.findViewById(R.id.title);
        String titleText = interaction.getTitle();
        if (titleText != null) {
            titleTextView.setText(titleText);
        }

        messageCenterHeader = viewActivity.findViewById(R.id.header_bar);
        headerDivider = viewActivity.findViewById(R.id.header_divider);
        int defaultColor = Util.getThemeColor(viewActivity, R.attr.colorPrimary);
        int brightColor = Util.lighter(defaultColor, 0.5f);
        headerDivider.setBackgroundColor(brightColor);
        messageCenterListView = (ListView) viewActivity.findViewById(R.id.message_list);
        messageCenterListView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
        messageCenterListView.setOnScrollListener(this);
        ((MessageCenterListView) messageCenterListView).setOnListViewResizeListener(this);
        messageCenterListView.setItemsCanFocus(true);
        final SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME,
                Context.MODE_PRIVATE);
        boolean showKeyboard = true;

        fab = viewActivity.findViewById(R.id.composing_fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addComposingArea();
                messageCenterListAdapter.setForceShowKeyboard(true);
                messageCenterViewHandler.sendEmptyMessage(MSG_SCROLL_TO_BOTTOM);
            }
        });

        profileButton = viewActivity.findViewById(R.id.profile);
        if (profileButton != null) {
            profileButton.setOnClickListener(new View.OnClickListener() {
                public void onClick(View view) {
                    // Only allow profile editing when not already editing profile or in message composing
                    if (whoCardItem == null && composingItem == null) {
                        hideProfileButton();
                        boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET,
                                false);

                        JSONObject data = new JSONObject();
                        try {
                            data.put("required", interaction.getWhoCardRequired());
                            data.put("trigger", "button");
                        } catch (JSONException e) {
                            //
                        }
                        EngagementModule.engageInternal(viewActivity, interaction,
                                MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString());

                        if (!bWhoCardSet) {
                            addWhoCard(WHO_CARD_MODE_INIT);
                        } else {
                            addWhoCard(WHO_CARD_MODE_EDIT);
                        }
                        messageCenterListAdapter.setForceShowKeyboard(true);
                        messageCenterViewHandler.sendEmptyMessage(MSG_SCROLL_TO_BOTTOM);
                    }
                }
            });
        }

        if (messageCenterListAdapter == null) {
            List<MessageCenterListItem> items = MessageManager.getMessageCenterListItems(viewActivity);
            prepareMessages(items);

            if (contextualMessage != null) {
                addContextualMessage();
                showKeyboard = false;
            }
            /* Add composing
            ** if the user was in composing mode before roatation
             */
            else if (composingViewSavedState != null) {
                addComposingArea();
            }
            /* Add who card
            ** if the user was in composing Who card mode before roatation
             */
            else if (pendingWhoCardName != null || pendingWhoCardEmail != null
                    || pendingWhoCardAvatarFile != null) {
                addWhoCard(pendingWhoCardMode);
            } else if (!checkAddWhoCardIfRequired()) {
                /* If there is only greeting message, show composing.
                 * If Who Card is required, show Who Card first
                 */
                if (messages.size() == 1) {
                    addComposingArea();
                    showKeyboard = false;
                } else {
                    // Finally check if status message need to be restored
                    addExpectationStatusIfNeeded();
                }
            } else {
                // Hide keyboard when Who Card is required and the 1st thing to show
                showKeyboard = false;
            }

            updateMessageSentStates(); // Force timestamp recompilation.
            messageCenterListAdapter = new MessageAdapter<MessageCenterListItem>(viewActivity, messages, this,
                    interaction);
            messageCenterListAdapter.setForceShowKeyboard(showKeyboard);
            messageCenterListView.setAdapter(messageCenterListAdapter);
            if (listViewSavedTopIndex != -1) {
                messageCenterListView.setSelectionFromTop(listViewSavedTopIndex, listViewSavedTopOffset);
            }
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        int index = messageCenterListView.getFirstVisiblePosition();
        View v = messageCenterListView.getChildAt(0);
        int top = (v == null) ? 0 : (v.getTop() - messageCenterListView.getPaddingTop());
        outState.putInt(LIST_TOP_INDEX, index);
        outState.putInt(LIST_TOP_OFFSET, top);
        outState.putParcelable(COMPOSING_EDITTEXT_STATE, saveEditTextInstanceState());
        outState.putParcelable(WHO_CARD_NAME, messageCenterListAdapter.getWhoCardNameState());
        outState.putParcelable(WHO_CARD_EMAIL, messageCenterListAdapter.getWhoCardEmailState());
        outState.putString(WHO_CARD_AVATAR_FILE, messageCenterListAdapter.getWhoCardAvatarFileName());
        outState.putInt(WHO_CARD_MODE, pendingWhoCardMode);
        if (contextualMessage == null) {
            interaction.clearContextualMessage();
        }
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
    }

    @Override
    public boolean onBackPressed(Activity activity) {
        DialogFragment myFrag = (DialogFragment) (((ApptentiveInternalActivity) viewActivity)
                .getSupportFragmentManager()).findFragmentByTag("preview_dialog");
        if (myFrag != null) {
            myFrag.dismiss();
        }
        cleanup();
        EngagementModule.engageInternal(viewActivity, interaction, MessageCenterInteraction.EVENT_NAME_CANCEL);
        return true;

    }

    public boolean cleanup() {
        savePendingComposingMessage();
        clearPendingMessageCenterPushNotification();
        clearComposingUi(null, null, 0);
        clearWhoCardUi(null, null, 0);
        // Set to null, otherwise they will hold reference to the activity context
        MessageManager.clearInternalOnMessagesUpdatedListeners();
        MessageManager.setAfterSendMessageListener(null);
        ApptentiveInternal.getAndClearCustomData();
        ApptentiveAttachmentLoader.getInstance().clearMemoryCache();
        return true;
    }

    public void onStart() {
        MessagePollingWorker.setMessageCenterInForeground(true);
    }

    public void onStop() {
        clearPendingMessageCenterPushNotification();
        MessagePollingWorker.setMessageCenterInForeground(false);
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
            case Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER: {
                if (data == null) {
                    Log.d("no image is picked");
                    return;
                }
                imagePickerLaunched = false;
                Uri uri;
                //Android SDK less than 19
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                    uri = data.getData();
                } else {
                    //for Android 4.4
                    uri = data.getData();
                    int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
                    viewActivity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
                }

                String originalPath = Util.getRealFilePathFromUri(viewActivity, uri);
                if (originalPath != null) {
                    /* If able to retrieve file path and creation time from uri, cache file name will be generated
                     * from the md5 of file path + creation time
                     */
                    long creation_time = Util.getContentCreationTime(viewActivity, uri);
                    Uri fileUri = Uri.fromFile(new File(originalPath));
                    File cacheDir = Util.getDiskCacheDir(viewActivity);
                    addAttachmentsToComposer(Arrays.asList(new ImageItem(originalPath,
                            Util.generateCacheFileFullPath(fileUri, cacheDir, creation_time),
                            Util.getMimeTypeFromUri(viewActivity, uri), creation_time)));
                } else {
                    /* If not able to get image file path due to not having READ_EXTERNAL_STORAGE permission,
                     * cache name will be generated from md5 of uri string
                     */
                    File cacheDir = Util.getDiskCacheDir(viewActivity);
                    String cachedFileName = Util.generateCacheFileFullPath(uri, cacheDir, 0);
                    addAttachmentsToComposer(Arrays.asList(new ImageItem(uri.toString(), cachedFileName,
                            Util.getMimeTypeFromUri(viewActivity, uri), 0)));
                }

                break;
            }
            default:
                break;
            }
        }
    }

    @Override
    public void onPause() {
        MessageManager.onPauseSending(0);
    }

    @Override
    public void onResume() {
        MessageManager.onResumeSending();
        /* imagePickerLaunched was set true when the picker intent was launched. If user had picked an image,
         * it woud have been set to false. Otherwise, it indicates the user tried to attach an image but
         * abandoned the image picker without picking anything
         */
        if (imagePickerLaunched) {
            EngagementModule.engageInternal(viewActivity, interaction,
                    MessageCenterInteraction.EVENT_NAME_ATTACHMENT_CANCEL);
            imagePickerLaunched = false;
        }
    }

    private void clearPendingMessageCenterPushNotification() {
        SharedPreferences prefs = viewActivity.getApplicationContext().getSharedPreferences(Constants.PREF_NAME,
                Context.MODE_PRIVATE);
        String pushData = prefs.getString(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION, null);
        if (pushData != null) {
            try {
                JSONObject pushJson = new JSONObject(pushData);
                ApptentiveInternal.PushAction action = ApptentiveInternal.PushAction.unknown;
                if (pushJson.has(ApptentiveInternal.PUSH_ACTION)) {
                    action = ApptentiveInternal.PushAction
                            .parse(pushJson.getString(ApptentiveInternal.PUSH_ACTION));
                }
                switch (action) {
                case pmc:
                    Log.i("Clearing pending Message Center push notification.");
                    prefs.edit().remove(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION).commit();
                    break;
                }
            } catch (JSONException e) {
                Log.w("Error parsing JSON from push notification.", e);
                MetricModule.sendError(viewActivity.getApplicationContext(), e, "Parsing Push notification",
                        pushData);
            }
        }
    }

    public void addContextualMessage() {
        // Clear any pending composing message to present an empty composing area
        clearPendingComposingMessage();
        clearStatus();
        messages.add(contextualMessage);
        // If checkAddWhoCardIfRequired returns true, it will add WhoCard
        if (!checkAddWhoCardIfRequired()) {
            addComposingArea();
        }
    }

    public void addComposingArea() {
        hideFab();
        hideProfileButton();
        clearStatus();
        actionBarItem = interaction.getComposerBar();
        messages.add(actionBarItem);
        composingItem = interaction.getComposerArea();
        messages.add(composingItem);
    }

    private boolean checkAddWhoCardIfRequired() {
        SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
        boolean bWhoCardSet = prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, false);
        if (interaction.getWhoCardRequestEnabled() && interaction.getWhoCardRequired()) {
            if (!bWhoCardSet) {
                addWhoCard(WHO_CARD_MODE_INIT);
                return true;
            } else {
                String savedEmail = Apptentive.getPersonEmail(viewActivity);
                if (TextUtils.isEmpty(savedEmail)) {
                    addWhoCard(WHO_CARD_MODE_EDIT);
                    return true;
                }
            }
        }
        return false;
    }

    public void addWhoCard(int mode) {
        hideProfileButton();
        if (!interaction.getWhoCardRequestEnabled()) {
            return;
        }
        pendingWhoCardMode = mode;
        hideFab();
        clearStatus();
        whoCardItem = (mode == WHO_CARD_MODE_INIT) ? interaction.getWhoCardInit() : interaction.getWhoCardEdit();
        messages.add(whoCardItem);
    }

    private boolean addExpectationStatusIfNeeded() {
        ApptentiveMessage apptentiveMessage = null;
        MessageCenterListItem message = messages.get(messages.size() - 1);

        if (message != null && message instanceof ApptentiveMessage) {
            apptentiveMessage = (ApptentiveMessage) message;
        }
        // Check if the last message in the view is a sent message
        if (apptentiveMessage != null && (apptentiveMessage.isOutgoingMessage())) {
            Double createdTime = apptentiveMessage.getCreatedAt();
            if (createdTime != null && createdTime > Double.MIN_VALUE) {
                MessageCenterStatus newItem = interaction.getRegularStatus();
                if (newItem != null && whoCardItem == null && composingItem == null) {
                    // Add expectation status message if the last is a sent
                    clearStatus();
                    statusItem = newItem;
                    messages.add(newItem);
                    return true;
                }
            }
        }
        return false;
    }

    public void addNewStatusItem(MessageCenterListItem item) {
        clearStatus();

        if (composingItem != null) {
            return;
        }

        statusItem = (MessageCenterStatus) item;
        messages.add(item);
        EngagementModule.engageInternal(viewActivity, interaction, MessageCenterInteraction.EVENT_NAME_STATUS);

    }

    public void addNewOutGoingMessageItem(ApptentiveMessage message) {
        clearStatus();

        messages.add(message);
        unsendMessagesCount++;

        isPaused = false;
        messageCenterListAdapter.setPaused(isPaused);
    }

    public void displayNewIncomingMessageItem(ApptentiveMessage message) {
        clearStatus();

        // Determine where to insert the new incoming message. It will be in front of any eidting
        // area, i.e. composing, Who Card ...
        int insertIndex = messages.size();
        if (composingItem != null) {
            // when in composing mode, there are composing action bar and composing area
            insertIndex -= 2;
            if (contextualMessage != null) {
                insertIndex--;
            }
        } else if (whoCardItem != null) {
            insertIndex -= 1;
            if (contextualMessage != null) {
                insertIndex--;
            }
        }
        messages.add(insertIndex, message);

        int firstIndex = messageCenterListView.getFirstVisiblePosition();
        int lastIndex = messageCenterListView.getLastVisiblePosition();
        boolean composingAreaTakesUpVisibleArea = firstIndex <= insertIndex && insertIndex < lastIndex;
        if (composingAreaTakesUpVisibleArea) {
            View v = messageCenterListView.getChildAt(0);
            int top = (v == null) ? 0 : v.getTop();
            updateMessageSentStates();
            // Restore the position of listview to composing view
            messageCenterViewHandler
                    .sendMessage(messageCenterViewHandler.obtainMessage(MSG_SCROLL_FROM_TOP, insertIndex, top));
        } else {
            updateMessageSentStates();
            messageCenterListAdapter.notifyDataSetChanged();
        }

    }

    private void clearStatus() {
        // Remove the status message whenever a new incoming message is added
        if (statusItem != null) {
            messages.remove(statusItem);
            statusItem = null;
        }
    }

    public void addAttachmentsToComposer(final List<ImageItem> images) {
        int numberOfExistingAttachments = imageAttachmentstList.size();
        ArrayList<ImageItem> uniqueImages = new ArrayList<ImageItem>();
        // only add new images, and filter out duplicates
        if (images != null && images.size() > 0) {
            for (ImageItem newImage : images) {
                boolean bDupFound = false;
                for (ImageItem existingImage : imageAttachmentstList) {
                    if (newImage.originalPath.equals(existingImage.originalPath)) {
                        bDupFound = true;
                        break;
                    }
                }
                if (bDupFound) {
                    continue;
                } else {
                    uniqueImages.add(newImage);
                }
            }
        }
        if (uniqueImages.size() == 0) {
            return;
        }
        imageAttachmentstList.addAll(uniqueImages);
        // New attachments are added to a composer with no prior attachment
        if (imageAttachmentstList.size() > 0 && numberOfExistingAttachments == 0) {
            EngagementModule.engageInternal(viewActivity, interaction,
                    MessageCenterInteraction.EVENT_NAME_ATTACHMENT_LIST_SHOWN);
        }
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_ATTACHMENT_ADD);

        View v = messageCenterListView.getChildAt(0);
        int top = (v == null) ? 0 : v.getTop();
        // Only update composing view if image is attached successfully
        messageCenterListAdapter.addImagestoComposer(uniqueImages);
        messageCenterListAdapter.setForceShowKeyboard(false);
        int firstIndex = messageCenterListView.getFirstVisiblePosition();

        messageCenterViewHandler
                .sendMessage(messageCenterViewHandler.obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top));

        onComposingBarCreated();
    }

    public void restoreSavedAttachmentsToComposer(final List<ImageItem> images) {
        imageAttachmentstList.clear();
        imageAttachmentstList.addAll(images);
        View v = messageCenterListView.getChildAt(0);
        int top = (v == null) ? 0 : v.getTop();
        // Only update composing view if image is attached successfully
        messageCenterListAdapter.addImagestoComposer(images);
        messageCenterListAdapter.setForceShowKeyboard(false);
        int firstIndex = messageCenterListView.getFirstVisiblePosition();

        messageCenterViewHandler
                .sendMessage(messageCenterViewHandler.obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top));

        onComposingBarCreated();
    }

    public void removeImageFromComposer(final int position) {
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_ATTACHMENT_DELETE);
        imageAttachmentstList.remove(position);
        messageCenterListAdapter.removeImageFromComposer(position);
        int count = imageAttachmentstList.size();
        // Show keyboard if all attachments have been removed
        messageCenterListAdapter.setForceShowKeyboard(count == 0);
        messageCenterViewHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS);

        onComposingBarCreated();

    }

    public void openNonImageAttachment(final ImageItem image) {
        if (image == null) {
            Log.d("No attachment argument.");
            return;
        }

        try {
            Util.openFileAttachment(viewActivity, image.originalPath, image.localCachePath, image.mimeType);
        } catch (Exception e) {
            Log.e("Error loading attachment", e);
        }
    }

    public void showAttachmentDialog(final ImageItem image) {
        if (image == null) {
            Log.d("No attachment argument.");
            return;
        }

        try {

            AttachmentPreviewDialog dialog = AttachmentPreviewDialog.newInstance(image);
            dialog.show(((ApptentiveInternalActivity) viewActivity).getSupportFragmentManager(), "preview_dialog");
        } catch (Exception e) {
            Log.e("Error loading attachment preview.", e);
        }
    }

    @SuppressWarnings("unchecked")
    // We should never get a message passed in that is not appropriate for the view it goes into.
    public synchronized void onMessageSent(ApptentiveHttpResponse response,
            final ApptentiveMessage apptentiveMessage) {
        if (response.isSuccessful() || response.isRejectedPermanently() || response.isBadPayload()) {
            messageCenterViewHandler
                    .sendMessage(messageCenterViewHandler.obtainMessage(MSG_MESSAGE_SENT, apptentiveMessage));
        }
    }

    public synchronized void onPauseSending(int reason) {
        messageCenterViewHandler.sendMessage(messageCenterViewHandler.obtainMessage(MSG_PAUSE_SENDING, reason, 0));
    }

    public synchronized void onResumeSending() {
        messageCenterViewHandler.sendEmptyMessage(MSG_RESUME_SENDING);
    }

    public void clearWhoCardUi(Animator.AnimatorListener al, ValueAnimator.AnimatorUpdateListener vl, long delay) {
        if (whoCardItem != null) {
            if (al != null) {
                deleteItemWithAnimation(messageCenterListAdapter.getWhoCardView(), al, vl, delay);
            } else {
                whoCardItem = null;
                pendingWhoCardName = null;
                pendingWhoCardEmail = null;
                pendingWhoCardAvatarFile = null;
                pendingWhoCardMode = 0;
                messageCenterListAdapter.clearWhoCard();
            }
        }
    }

    public void clearComposingUi(Animator.AnimatorListener al, ValueAnimator.AnimatorUpdateListener vl,
            long delay) {
        if (composingItem != null) {
            if (al != null) {
                deleteItemWithAnimation(messageCenterListAdapter.getComposingActionBarView(), null, null, delay);
                deleteItemWithAnimation(messageCenterListAdapter.getComposingAreaView(), al, vl, delay);
            } else {
                if (contextualMessage != null) {
                    messages.remove(contextualMessage);
                    contextualMessage = null;
                }
                messages.remove(actionBarItem);
                messages.remove(composingItem);
                actionBarItem = null;
                composingItem = null;
                messageEditText = null;
                messageCenterListAdapter.clearComposing();
                messageCenterListAdapter.notifyDataSetChanged();
                showFab();
            }
        }
    }

    @Override
    public void onComposingBarCreated() {
        MessageCenterComposingActionBarView barView = messageCenterListAdapter.getComposingActionBarView();
        if (barView != null) {
            barView.showConfirmation = true;
            int attachmentCount = imageAttachmentstList.size();
            if (attachmentCount == 0) {
                Editable content = getPendingComposingContent();
                final String messageText = (content != null) ? content.toString().trim() : "";
                barView.showConfirmation = !(messageText.isEmpty());
            }

            if (attachmentCount == viewActivity.getResources()
                    .getInteger(R.integer.apptentive_image_grid_default_attachments_total)) {
                AnimationUtil.fadeOutGone(barView.attachButton);
            } else {
                if (barView.attachButton.getVisibility() != View.VISIBLE) {
                    AnimationUtil.fadeIn(barView.attachButton, null);
                }
            }

            if (barView.showConfirmation == true) {
                barView.sendButton.setEnabled(true);
                barView.sendButton.setColorFilter(
                        Util.getThemeColorFromAttrOrRes(viewActivity, R.attr.colorAccent, R.color.colorAccent));
            } else {
                barView.sendButton.setEnabled(false);
                barView.sendButton.setColorFilter(Util.getThemeColorFromAttrOrRes(viewActivity,
                        R.attr.apptentive_material_disabled_icon, R.color.apptentive_material_dark_disabled_icon));
            }
        }
    }

    @Override
    public void onComposingViewCreated() {
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN);
        messageEditText = messageCenterListAdapter.getEditTextInComposing();
        SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
        // Restore composing text editing state, such as cursor position, after rotation
        if (composingViewSavedState != null) {
            messageEditText.onRestoreInstanceState(composingViewSavedState);
            composingViewSavedState = null;
            SharedPreferences.Editor editor = prefs.edit();
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).commit();
        }
        // Restore composing text
        if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE)) {
            String messageText = prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE, null);
            if (messageText != null) {
                messageEditText.setText(messageText);
            }
            // Stored pending composing text has been restored, remove it from the persistent storage
            SharedPreferences.Editor editor = prefs.edit();
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).commit();
        }

        // Restore composing attachments
        if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)) {
            JSONArray jArray = null;
            try {
                jArray = new JSONArray(
                        prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, ""));
            } catch (JSONException e) {
                e.printStackTrace();
            }
            if (jArray != null && jArray.length() > 0) {
                if (savedAttachmentstList == null) {
                    savedAttachmentstList = new ArrayList<ImageItem>();
                }
                for (int i = 0; i < jArray.length(); i++) {
                    try {
                        JSONObject json = jArray.getJSONObject(i);
                        if (json != null) {
                            savedAttachmentstList.add(new ImageItem(json));
                        }
                    } catch (JSONException e) {
                        continue;
                    }
                }
            }
            // Stored pending attachemnts have been restored, remove it from the persistent storage
            SharedPreferences.Editor editor = prefs.edit();
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).commit();
        }

        if (savedAttachmentstList != null) {
            restoreSavedAttachmentsToComposer(savedAttachmentstList);
            savedAttachmentstList = null;
        }
        messageCenterListView.setPadding(0, 0, 0, 0);
    }

    @Override
    public void onWhoCardViewCreated(EditText nameEditText, EditText emailEditText) {
        if (pendingWhoCardName != null) {
            nameEditText.onRestoreInstanceState(pendingWhoCardName);
            pendingWhoCardName = null;
        }
        if (pendingWhoCardEmail != null) {
            emailEditText.onRestoreInstanceState(pendingWhoCardEmail);
            pendingWhoCardEmail = null;
        }
        messageCenterListView.setPadding(0, 0, 0, 0);
    }

    @Override
    public void beforeComposingTextChanged(CharSequence str) {

    }

    @Override
    public void onComposingTextChanged(CharSequence str) {
    }

    @Override
    public void afterComposingTextChanged(String str) {
        onComposingBarCreated();
    }

    @Override
    public void onCancelComposing() {
        messageCenterListAdapter.setForceShowKeyboard(false);
        Util.hideSoftKeyboard(viewActivity, viewActivity.findViewById(android.R.id.content));

        JSONObject data = new JSONObject();
        try {
            Editable content = getPendingComposingContent();
            int bodyLength = (content != null) ? content.toString().trim().length() : 0;
            data.put("body_length", bodyLength);
        } catch (JSONException e) {
            //
        }
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_COMPOSE_CLOSE, data.toString());

        clearComposingUi(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (contextualMessage != null) {
                    messages.remove(contextualMessage);
                    contextualMessage = null;
                }
                messages.remove(actionBarItem);
                messages.remove(composingItem);
                actionBarItem = null;
                composingItem = null;
                messageEditText = null;
                messageCenterListAdapter.clearComposing();
                addExpectationStatusIfNeeded();
                messageCenterListAdapter.notifyDataSetChanged();
                imageAttachmentstList.clear();
                showFab();
                showProfileButton();
                // messageEditText has been set to null, pending composing message will reset
                clearPendingComposingMessage();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }
        }, null, DEFAULT_DELAYMILLIS);
        //clearComposingUi(null, null, 0);
    }

    @Override
    public void onFinishComposing() {
        messageCenterListAdapter.setForceShowKeyboard(false);
        Util.hideSoftKeyboard(viewActivity, viewActivity.findViewById(android.R.id.content));
        if (contextualMessage != null) {
            unsendMessagesCount++;
            MessageManager.sendMessage(viewActivity.getApplicationContext(), contextualMessage);
            contextualMessage = null;
        }
        Editable content = getPendingComposingContent();
        final String messageText = (content != null) ? content.toString().trim() : "";
        final ArrayList<ImageItem> messageAttachments = new ArrayList<ImageItem>();
        messageAttachments.addAll(imageAttachmentstList);
        // Close all composing UI
        clearComposingUi(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                messages.remove(actionBarItem);
                messages.remove(composingItem);
                actionBarItem = null;
                composingItem = null;
                messageEditText = null;
                messageCenterListAdapter.clearComposing();
                messageCenterListAdapter.notifyDataSetChanged();
                clearPendingComposingMessage();
                // Send out the new message. The delay is added to ensure the CardView showing animation
                // is visible after the keyboard is hidden
                if (!messageText.isEmpty() || imageAttachmentstList.size() != 0) {
                    Bundle b = new Bundle();
                    b.putString(COMPOSING_EDITTEXT_STATE, messageText);
                    b.putParcelableArrayList(COMPOSING_ATTACHMENTS, messageAttachments);
                    Message msg = messageCenterViewHandler.obtainMessage(MSG_START_SENDING, messageText);
                    msg.setData(b);
                    messageCenterViewHandler.sendMessageDelayed(msg, DEFAULT_DELAYMILLIS);
                }

                imageAttachmentstList.clear();
                showFab();
                showProfileButton();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }
        }, null, DEFAULT_DELAYMILLIS);
    }

    @Override
    public void onSubmitWhoCard(String buttonLabel) {
        JSONObject data = new JSONObject();
        try {
            data.put("required", interaction.getWhoCardRequired());
            data.put("button_label", buttonLabel);
        } catch (JSONException e) {
            //
        }
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_PROFILE_SUBMIT, data.toString());

        cleanupWhoCard();
    }

    @Override
    public void onCloseWhoCard(String buttonLabel) {
        JSONObject data = new JSONObject();
        try {
            data.put("required", interaction.getWhoCardRequired());
            data.put("button_label", buttonLabel);
        } catch (JSONException e) {
            //
        }
        EngagementModule.engageInternal(viewActivity, interaction,
                MessageCenterInteraction.EVENT_NAME_PROFILE_CLOSE, data.toString());

        cleanupWhoCard();
    }

    public void cleanupWhoCard() {
        messageCenterListAdapter.setForceShowKeyboard(false);
        Util.hideSoftKeyboard(viewActivity, viewActivity.findViewById(android.R.id.content));
        clearWhoCardUi(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                messages.remove(whoCardItem);
                whoCardItem = null;
                pendingWhoCardName = null;
                pendingWhoCardEmail = null;
                pendingWhoCardAvatarFile = null;
                pendingWhoCardMode = 0;
                messageCenterListAdapter.clearWhoCard();
                addExpectationStatusIfNeeded();
                messageCenterListAdapter.notifyDataSetChanged();
                saveWhoCardSetState();
                // If Who card is required, it might be displayed before proceeding to composing, for instance
                // when there was a contextual message or it was the first message. We need to resume composing
                // after dismissing Who Card
                if ((messages.size() == 1 || contextualMessage != null) && interaction.getWhoCardRequired()) {
                    addComposingArea();
                    messageCenterListAdapter.setForceShowKeyboard(true);
                    messageCenterViewHandler.sendEmptyMessageDelayed(MSG_MESSAGE_NOTIFY_UPDATE,
                            DEFAULT_DELAYMILLIS);
                } else {
                    showFab();
                    showProfileButton();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }
        }, null, DEFAULT_DELAYMILLIS);
    }

    @Override
    public void onNewMessageReceived(final CompoundMessage apptentiveMsg) {
        messageCenterViewHandler
                .sendMessage(messageCenterViewHandler.obtainMessage(MSG_MESSAGE_ADD_INCOMING, apptentiveMsg));
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

    }

    /* Show header elevation when listview can scroll up; flatten header when listview
     * scrolls to the top; For pre-llolipop devices, fallback to a divider
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        boolean bCanScrollUp;
        if (android.os.Build.VERSION.SDK_INT < 14) {
            bCanScrollUp = view.getChildCount() > 0
                    && (view.getFirstVisiblePosition() > 0 || view.getChildAt(0).getTop() < view.getPaddingTop());
        } else {
            bCanScrollUp = ViewCompat.canScrollVertically(view, -1);
        }
        if (bCanScrollUp) {
            if (android.os.Build.VERSION.SDK_INT > 20) {
                messageCenterHeader.setElevation(8);
            } else {
                headerDivider.setVisibility(View.VISIBLE);
            }
        } else {
            if (android.os.Build.VERSION.SDK_INT > 20) {
                messageCenterHeader.setElevation(0);
            } else {
                headerDivider.setVisibility(View.GONE);
            }
        }
    }

    @Override
    public void OnListViewResize(int w, int h, int oldw, int oldh) {
        // detect keyboard launching
        if (oldh > h) {
            if (composingItem != null) {
                // When keyboard is up, adjust the scolling such that the cursor is always visible
                final int firstIndex = messageCenterListView.getFirstVisiblePosition();
                int lastIndex = messageCenterListView.getLastVisiblePosition();
                View v = messageCenterListView.getChildAt(lastIndex - firstIndex);
                int top = (v == null) ? 0 : v.getTop();
                if (messageEditText != null) {
                    int pos = messageEditText.getSelectionStart();
                    Layout layout = messageEditText.getLayout();
                    int line = layout.getLineForOffset(pos);
                    int baseline = layout.getLineBaseline(line);
                    int ascent = layout.getLineAscent(line);
                    messageCenterViewHandler.sendMessage(messageCenterViewHandler.obtainMessage(MSG_SCROLL_FROM_TOP,
                            lastIndex, Math.max(top - (oldh - h), baseline - ascent)));
                }
            }
        }
    }

    /* Callback when the attach button is clicked
     *
     */
    @Override
    public void onAttachImage() {
        try {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {//prior Api level 19
                Intent intent = new Intent();
                intent.setType("image/*");
                intent.setAction(Intent.ACTION_GET_CONTENT);
                Intent chooserIntent = Intent.createChooser(intent, null);
                viewActivity.startActivityForResult(chooserIntent, Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER);
            } else {
                Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.addCategory(Intent.CATEGORY_OPENABLE);
                intent.setType("image/*");
                Intent chooserIntent = Intent.createChooser(intent, null);
                viewActivity.startActivityForResult(chooserIntent, Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER);
            }
            imagePickerLaunched = true;
        } catch (Exception e) {
            e.printStackTrace();
            imagePickerLaunched = false;
            Log.d("can't launch image picker");
        }
    }

    private void saveWhoCardSetState() {
        SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_SET, true);
        editor.apply();
    }

    // Retrieve the content from the composing area
    public Editable getPendingComposingContent() {
        return (messageEditText == null) ? null : messageEditText.getText();
    }

    public void savePendingComposingMessage() {
        Editable content = getPendingComposingContent();
        SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        if (content != null) {
            editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE,
                    content.toString().trim());
        } else {
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE);
        }

        JSONArray jArray = new JSONArray();
        // Save pending attachment
        for (ImageItem pendingAttachment : imageAttachmentstList) {
            JSONObject jobject = pendingAttachment.toJSON();
            if (jobject != null) {
                jArray.put(jobject);
            }
        }

        if (jArray.length() > 0) {
            editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, jArray.toString());
        } else {
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS);
        }
        editor.apply();
    }

    /* When no composing view is presented in the list view, calling this method
     * will clear the pending composing message previously saved in shared preference
     */
    public void clearPendingComposingMessage() {
        SharedPreferences prefs = viewActivity.getSharedPreferences(Constants.PREF_NAME, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE);
        editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS);
        editor.apply();
    }

    private Parcelable saveEditTextInstanceState() {
        savePendingComposingMessage();
        if (messageEditText != null) {
            // Hide keyboard if the keyboard was up prior to rotation
            Util.hideSoftKeyboard(viewActivity, viewActivity.findViewById(android.R.id.content));
            return messageEditText.onSaveInstanceState();
        }
        return null;
    }

    Set<String> dateStampsSeen = new HashSet<String>();

    public void updateMessageSentStates() {
        dateStampsSeen.clear();
        MessageCenterUtil.CompoundMessageCommonInterface lastSent = null;
        Set<String> uniqueNonce = new HashSet<String>();
        Iterator<MessageCenterListItem> messageIterator = messages.iterator();
        while (messageIterator.hasNext()) {
            MessageCenterListItem message = messageIterator.next();
            if (message instanceof ApptentiveMessage) {
                /* Check if there is any duplicate messages and remove if found.
                * add() of a Set returns false if the element already exists.
                 */
                if (!uniqueNonce.add(((ApptentiveMessage) message).getNonce())) {
                    messageIterator.remove();
                    continue;
                }
                // Update timestamps
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) message;
                Double sentOrReceivedAt = apptentiveMessage.getCreatedAt();
                String dateStamp = createDatestamp(sentOrReceivedAt);
                if (dateStamp != null) {
                    if (dateStampsSeen.add(dateStamp)) {
                        apptentiveMessage.setDatestamp(dateStamp);
                    } else {
                        apptentiveMessage.clearDatestamp();
                    }
                }

                //Find last sent
                if (apptentiveMessage.isOutgoingMessage()) {
                    if (sentOrReceivedAt != null && sentOrReceivedAt > Double.MIN_VALUE) {
                        lastSent = (MessageCenterUtil.CompoundMessageCommonInterface) apptentiveMessage;
                        lastSent.setLastSent(false);
                    }

                }
            }
        }

        if (lastSent != null) {
            lastSent.setLastSent(true);
        }
    }

    protected String createDatestamp(Double seconds) {
        if (seconds != null && seconds > Double.MIN_VALUE) {
            Date date = new Date(Math.round(seconds * 1000));
            DateFormat mediumDateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
            return mediumDateFormat.format(date);
        }
        return null;
    }

    private void deleteItemWithAnimation(final View v, final Animator.AnimatorListener al,
            final ValueAnimator.AnimatorUpdateListener vl, long delay) {
        if (v == null) {
            return;
        }
        AnimatorSet animatorSet = AnimationUtil.buildListViewRowRemoveAnimator(v, al, vl);
        animatorSet.setStartDelay(delay);
        animatorSet.start();
    }

    private void showFab() {
        float scale = viewActivity.getResources().getDisplayMetrics().density;
        int dpAsPixels = (int) (viewActivity.getResources()
                .getDimension(R.dimen.apptentive_message_center_bottom_padding) * scale + 0.5f);
        messageCenterListView.setPadding(0, 0, 0, dpAsPixels);
        AnimationUtil.scaleFadeIn(fab);
    }

    private void hideFab() {
        AnimationUtil.scaleFadeOutGone(fab);
    }

    private void showProfileButton() {
        AnimationUtil.fadeIn(profileButton, null);
    }

    private void hideProfileButton() {
        AnimationUtil.fadeOutGone(profileButton);
    }

    /*
     * Messages returned from the database was sorted on KEY_ID, which was generated by server
     * with seconds resolution. If messages were received by server within a second, messages may be out of order
     * This method uses insertion sort to re-sort the messages retrieved from the database
     */
    private void prepareMessages(final List<MessageCenterListItem> originalItems) {
        messages.clear();
        unsendMessagesCount = 0;
        // Loop through each message item retrieved from database
        for (MessageCenterListItem item : originalItems) {
            if (item instanceof ApptentiveMessage) {
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) item;
                Double createdAt = apptentiveMessage.getCreatedAt();
                if (apptentiveMessage.isOutgoingMessage() && createdAt == null) {
                    unsendMessagesCount++;
                }

                /*
                 * Find proper location to insert into the messages list of the listview.
                 */
                ListIterator<MessageCenterListItem> listIterator = messages.listIterator();
                ApptentiveMessage next = null;
                while (listIterator.hasNext()) {
                    next = (ApptentiveMessage) listIterator.next();
                    Double nextCreatedAt = next.getCreatedAt();
                    // For unsent and dropped message, move the iterator to the end, and append there
                    if (createdAt == null || createdAt <= Double.MIN_VALUE) {
                        continue;
                    }
                    // next message has not received by server or received, but has a later created_at time
                    if (nextCreatedAt == null || nextCreatedAt > createdAt) {
                        break;
                    }
                }

                if (next == null || next.getCreatedAt() == null || createdAt == null
                        || next.getCreatedAt() <= createdAt || createdAt <= Double.MIN_VALUE) {
                    listIterator.add(item);
                } else {
                    // Add in front of the message that has later created_at time
                    listIterator.set(item);
                    listIterator.add(next);
                }
            }
        }
        // Finally, add greeting message
        messages.add(0, interaction.getGreeting());
    }

    @Override
    public void onClickAttachment(final int position, final ImageItem image) {
        if (Util.isMimeTypeImage(image.mimeType)) {
            // "+" placeholder is clicked
            if (TextUtils.isEmpty(image.originalPath)) {
                onAttachImage();
            } else {
                // an image thumbnail is clicked
                showAttachmentDialog(image);
            }
        } else {
            // a generic attachment icon is clicked
            openNonImageAttachment(image);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == Constants.REQUEST_READ_STORAGE_PERMISSION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                onAttachImage();
            }
        }
    }

    /*
     * Called when attachment overlayed "selection" ui is tapped. The "selection" ui could be selection checkbox
     * or close button
     */
    @Override
    public void onImageSelected(int index) {
        removeImageFromComposer(index);
    }

    @Override
    public void onImageUnselected(String path) {

    }

    @Override
    public void onCameraShot(File imageFile) {

    }
}