com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.apptentive.android.sdk.module.engagement.interaction.fragment.MessageCenterFragment.java

Source

/*
 * Copyright (c) 2016, 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.engagement.interaction.fragment;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
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.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.text.Editable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.EditText;

import com.apptentive.android.sdk.Apptentive;
import com.apptentive.android.sdk.ApptentiveInternal;
import com.apptentive.android.sdk.ApptentiveLog;
import com.apptentive.android.sdk.ApptentiveViewActivity;
import com.apptentive.android.sdk.ApptentiveViewExitType;
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.messagecenter.MessageManager;
import com.apptentive.android.sdk.module.messagecenter.OnListviewItemActionListener;
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.ContextMessage;
import com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem;
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.WhoCard;
import com.apptentive.android.sdk.module.messagecenter.view.AttachmentPreviewDialog;
import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerView;
import com.apptentive.android.sdk.module.messagecenter.view.MessageCenterRecyclerViewAdapter;
import com.apptentive.android.sdk.module.messagecenter.view.holder.MessageComposerHolder;
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.image.ApptentiveAttachmentLoader;
import com.apptentive.android.sdk.util.image.ApptentiveImageGridView;
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.lang.ref.WeakReference;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;

import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_COMPOSER;
import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_CONTEXT;
import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.MESSAGE_OUTGOING;
import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.STATUS;
import static com.apptentive.android.sdk.module.messagecenter.model.MessageCenterListItem.WHO_CARD;

public class MessageCenterFragment extends ApptentiveBaseFragment<MessageCenterInteraction>
        implements OnListviewItemActionListener, MessageManager.AfterSendMessageListener,
        MessageManager.OnNewIncomingMessagesListener, OnMenuItemClickListener, AbsListView.OnScrollListener,
        ImageGridViewAdapter.Callback {

    private MenuItem profileMenuItem;
    private boolean bShowProfileMenuItem = true;

    // keys used to save instance in the event of rotation
    private final static String LIST_TOP_INDEX = "key_list_top_index_state";
    private final static String LIST_TOP_OFFSET = "key_list_top_offset_state";
    private final static String COMPOSING_EDITTEXT_STATE = "key_edit_text_state";
    private final static String WHO_CARD_MODE = "key_who_card_mode_state";
    private final static String WHO_CARD_NAME = "key_who_card_name_state";
    private final static String WHO_CARD_EMAIL = "key_who_card_email_state";
    private final static String WHO_CARD_AVATAR_FILE = "key_who_card_avatar_state";

    private final static String DIALOG_IMAGE_PREVIEW = "imagePreviewDialog";

    private final static long DEFAULT_DELAYMILLIS = 200;

    /* Fragment.getActivity() may return null if not attached.
     * hostingActivityRef is always set in onAttach()
     * Keeping a cached weak reference ensures it's safe to use
     */
    private WeakReference<Activity> hostingActivityRef;

    private View fab;

    private ArrayList<MessageCenterListItem> listItems = new ArrayList<MessageCenterListItem>();
    private MessageCenterRecyclerViewAdapter messageCenterRecyclerViewAdapter;
    private MessageCenterRecyclerView messageCenterRecyclerView;

    // Holder and view references
    private MessageComposerHolder composer;
    private EditText composerEditText;
    private EditText whoCardNameEditText;
    private EditText whoCardEmailEditText;
    private Parcelable composingViewSavedState;
    /*
     * 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 imagePickerStillOpen = false;
    private ArrayList<ImageItem> pendingAttachments = new ArrayList<ImageItem>();

    private boolean pendingWhoCardMode;
    private String pendingWhoCardAvatarFile;
    private Parcelable pendingWhoCardName;
    private Parcelable pendingWhoCardEmail;

    private boolean forceShowKeyboard;

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

    private int listViewSavedTopIndex = -1;
    private int listViewSavedTopOffset;

    // FAB y-offset in pixels from the bottom edge
    private int fabPaddingPixels;

    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_ADD_WHOCARD = 8;
    protected static final int MSG_MESSAGE_ADD_COMPOSING = 9;
    protected static final int MSG_SEND_PENDING_CONTEXT_MESSAGE = 10;
    protected static final int MSG_REMOVE_COMPOSER = 11;
    protected static final int MSG_REMOVE_STATUS = 12;
    protected static final int MSG_OPT_INSERT_REGULAR_STATUS = 13;
    protected static final int MSG_MESSAGE_REMOVE_WHOCARD = 14;
    protected static final int MSG_ADD_CONTEXT_MESSAGE = 15;
    protected static final int MSG_ADD_GREETING = 16;
    protected static final int MSG_ADD_STATUS_ERROR = 17;
    protected static final int MSG_REMOVE_ATTACHMENT = 18;

    private MessageCenterFragment.MessagingActionHandler messagingActionHandler;

    public static MessageCenterFragment newInstance(Bundle bundle) {
        MessageCenterFragment mcFragment = new MessageCenterFragment();
        mcFragment.setArguments(bundle);
        return mcFragment;
    }

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Make Message Center fragment retain its instance on orientation change
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        listViewSavedTopIndex = (savedInstanceState == null) ? -1 : savedInstanceState.getInt(LIST_TOP_INDEX);
        listViewSavedTopOffset = (savedInstanceState == null) ? 0 : savedInstanceState.getInt(LIST_TOP_OFFSET);
        composingViewSavedState = (savedInstanceState == null) ? null
                : savedInstanceState.getParcelable(COMPOSING_EDITTEXT_STATE);
        pendingWhoCardName = (savedInstanceState == null) ? null : savedInstanceState.getParcelable(WHO_CARD_NAME);
        pendingWhoCardEmail = (savedInstanceState == null) ? null
                : savedInstanceState.getParcelable(WHO_CARD_EMAIL);
        pendingWhoCardAvatarFile = (savedInstanceState == null) ? null
                : savedInstanceState.getString(WHO_CARD_AVATAR_FILE);
        pendingWhoCardMode = savedInstanceState != null && savedInstanceState.getBoolean(WHO_CARD_MODE);
        return inflater.inflate(R.layout.apptentive_message_center, container, false);
    }

    public void onViewCreated(View view, Bundle onSavedInstanceState) {
        super.onViewCreated(view, onSavedInstanceState);
        boolean isInitialViewCreation = (onSavedInstanceState == null);
        /* When isInitialViewCreation is false, the view is being recreated after orientation change.
         * Because the fragment is set to be retained after orientation change, setup() will reuse the retained states
         */
        setup(view, isInitialViewCreation);

        MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager();
        // This listener will run when messages are retrieved from the server, and will start a new thread to update the view.
        mgr.addInternalOnMessagesUpdatedListener(this);
        // Give the MessageCenterView a callback when a message is sent.
        mgr.setAfterSendMessageListener(this);

        // Needed to prevent the window from being pushed up when a text input area is focused.
        getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

        // Restore listview scroll offset to where it was before rotation
        if (listViewSavedTopIndex != -1) {
            messagingActionHandler.sendMessageDelayed(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP,
                    listViewSavedTopIndex, listViewSavedTopOffset), DEFAULT_DELAYMILLIS);
        } else {
            messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        hostingActivityRef = new WeakReference<Activity>((Activity) context);
        messagingActionHandler = new MessageCenterFragment.MessagingActionHandler(this);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        // messageCenterRecyclerViewAdapter holds a reference to fragment context. Need to set it to null in this and other Views to prevent a memory leak.
        messageCenterRecyclerViewAdapter = null;
        messageCenterRecyclerView.setAdapter(null);
        composer = null;
        composerEditText = null;
        whoCardNameEditText = null;
        whoCardEmailEditText = null;
    }

    public void onStart() {
        super.onStart();
        ApptentiveInternal.getInstance().getMessageManager().setMessageCenterInForeground(true);
    }

    public void onStop() {
        super.onStop();
        clearPendingMessageCenterPushNotification();
        ApptentiveInternal.getInstance().getMessageManager().setMessageCenterInForeground(false);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
            case Constants.REQUEST_CODE_CLOSE_COMPOSING_CONFIRMATION: {
                onCancelComposing();
                break;
            }
            case Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER: {
                if (data == null) {
                    ApptentiveLog.d("no image is picked");
                    return;
                }
                imagePickerStillOpen = false;
                Uri uri;
                Activity hostingActivity = hostingActivityRef.get();
                //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;
                    if (hostingActivity != null) {
                        hostingActivity.getContentResolver().takePersistableUriPermission(uri, takeFlags);
                    }
                }

                EngagementModule.engageInternal(getActivity(), interaction,
                        MessageCenterInteraction.EVENT_NAME_ATTACH);

                String originalPath = Util.getRealFilePathFromUri(hostingActivity, 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(hostingActivity, uri);
                    Uri fileUri = Uri.fromFile(new File(originalPath));
                    File cacheDir = Util.getDiskCacheDir(hostingActivity);
                    addAttachmentsToComposer(new ImageItem(originalPath,
                            Util.generateCacheFileFullPath(fileUri, cacheDir, creation_time),
                            Util.getMimeTypeFromUri(hostingActivity, 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(hostingActivity);
                    String cachedFileName = Util.generateCacheFileFullPath(uri, cacheDir, 0);
                    addAttachmentsToComposer(new ImageItem(uri.toString(), cachedFileName,
                            Util.getMimeTypeFromUri(hostingActivity, uri), 0));
                }

                break;
            }
            default:
                break;
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        ApptentiveInternal.getInstance().getMessageManager()
                .pauseSending(MessageManager.SEND_PAUSE_REASON_ACTIVITY_PAUSE);
    }

    @Override
    public void onResume() {
        super.onResume();
        ApptentiveInternal.getInstance().getMessageManager().resumeSending();

        /* imagePickerStillOpen 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 (imagePickerStillOpen) {
            EngagementModule.engageInternal(hostingActivityRef.get(), interaction,
                    MessageCenterInteraction.EVENT_NAME_ATTACHMENT_CANCEL);
            imagePickerStillOpen = false;
        }
    }

    protected int getMenuResourceId() {
        return R.menu.apptentive_message_center;
    }

    @Override
    protected void attachFragmentMenuListeners(Menu menu) {
        profileMenuItem = menu.findItem(R.id.profile);
        profileMenuItem.setOnMenuItemClickListener(this);
        updateMenuVisibility();
    }

    @Override
    protected void updateMenuVisibility() {
        profileMenuItem.setVisible(bShowProfileMenuItem);
        profileMenuItem.setEnabled(bShowProfileMenuItem);
    }

    private void setup(View rootView, boolean isInitialViewCreation) {
        boolean addedAnInteractiveCard = false;

        messageCenterRecyclerView = (MessageCenterRecyclerView) rootView
                .findViewById(R.id.message_center_recycler_view);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            messageCenterRecyclerView.setNestedScrollingEnabled(true);
        }
        LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext());
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        messageCenterRecyclerView.setLayoutManager(layoutManager);

        fab = rootView.findViewById(R.id.composing_fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                forceShowKeyboard = true;
                addComposingCard();
            }
        });

        messageCenterRecyclerViewAdapter = new MessageCenterRecyclerViewAdapter(this, this, interaction, listItems);

        if (isInitialViewCreation) {
            List<MessageCenterListItem> items = ApptentiveInternal.getInstance().getMessageManager()
                    .getMessageCenterListItems();
            if (items != null) {
                // Get message list from DB, and use this as the starting point for the listItems array.
                prepareMessages(items);
            }

            String contextMessageBody = interaction.getContextualMessageBody();
            if (contextMessageBody != null) {
                // Clear any pending composing message to present an empty composing area
                clearPendingComposingMessage();
                messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
                messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_ADD_CONTEXT_MESSAGE,
                        new ContextMessage(contextMessageBody)));
                // If checkAddWhoCardIfRequired returns true, it will add WhoCard, otherwise add composing card
                if (!checkAddWhoCardIfRequired()) {
                    addedAnInteractiveCard = true;
                    forceShowKeyboard = false;
                    addComposingCard();
                }
            }

            /* Add who card with pending contents
            ** Pending contents would be saved if the user was in composing Who card mode and exited through back button
             */
            else if (pendingWhoCardName != null || pendingWhoCardEmail != null
                    || pendingWhoCardAvatarFile != null) {
                addedAnInteractiveCard = true;
                addWhoCard(pendingWhoCardMode);
            } else if (!checkAddWhoCardIfRequired()) {
                /* If there are no items in the list, then it means that the Greeting will be added, but nothing else.
                 * In that case, show the Composer, because Message Center hasn't been opened before.
                 * If Who Card is required, show Who Card first.
                 */
                if (listItems.size() == 0) {
                    addedAnInteractiveCard = true;
                    addComposingCard();
                } else {
                    // Finally check if status message need to be restored
                    addExpectationStatusIfNeeded();
                }
            }
        } else {
            // Need to account for an input view that was added before orientation change, etc.
            if (listItems != null) {
                for (MessageCenterListItem item : listItems) {
                    if (item.getListItemType() == MESSAGE_COMPOSER || item.getListItemType() == WHO_CARD) {
                        addedAnInteractiveCard = true;
                    }
                }
            }
        }

        messageCenterRecyclerView.setAdapter(messageCenterRecyclerViewAdapter);

        // Calculate FAB y-offset
        fabPaddingPixels = calculateFabPadding(rootView.getContext());

        if (!addedAnInteractiveCard) {
            showFab();
        }

        // Retrieve any saved attachments
        final SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        if (prefs.contains(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)) {
            JSONArray savedAttachmentsJsonArray = null;
            try {
                savedAttachmentsJsonArray = new JSONArray(
                        prefs.getString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS, ""));
            } catch (JSONException e) {
                e.printStackTrace();
            }
            if (savedAttachmentsJsonArray != null && savedAttachmentsJsonArray.length() > 0) {
                if (pendingAttachments == null) {
                    pendingAttachments = new ArrayList<ImageItem>();
                } else {
                    pendingAttachments.clear();
                }
                for (int i = 0; i < savedAttachmentsJsonArray.length(); i++) {
                    try {
                        JSONObject savedAttachmentJson = savedAttachmentsJsonArray.getJSONObject(i);
                        if (savedAttachmentJson != null) {
                            pendingAttachments.add(new ImageItem(savedAttachmentJson));
                        }
                    } catch (JSONException e) {
                        continue;
                    }
                }
            }
            // Stored pending attachments have been restored, remove it from the persistent storage
            SharedPreferences.Editor editor = prefs.edit();
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).apply();
        }
        updateMessageSentStates();
    }

    public boolean onMenuItemClick(MenuItem menuItem) {
        int menuItemId = menuItem.getItemId();

        if (menuItemId == R.id.profile) {
            JSONObject data = new JSONObject();
            try {
                data.put("required", interaction.getWhoCardRequired());
                data.put("trigger", "button");
            } catch (JSONException e) {
                //
            }
            EngagementModule.engageInternal(hostingActivityRef.get(), interaction,
                    MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString());

            boolean whoCardDisplayedBefore = wasWhoCardAsPreviouslyDisplayed();
            forceShowKeyboard = true;
            addWhoCard(!whoCardDisplayedBefore);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        savePendingComposingMessage();
        //int index = messageCenterRecyclerView.getFirstVisiblePosition();
        View v = messageCenterRecyclerView.getChildAt(0);
        int top = (v == null) ? 0 : (v.getTop() - messageCenterRecyclerView.getPaddingTop());
        outState.putInt(LIST_TOP_OFFSET, top);
        outState.putParcelable(COMPOSING_EDITTEXT_STATE, saveEditTextInstanceState());
        if (messageCenterRecyclerViewAdapter != null) {
            outState.putParcelable(WHO_CARD_NAME,
                    whoCardNameEditText != null ? whoCardNameEditText.onSaveInstanceState() : null);
            outState.putParcelable(WHO_CARD_EMAIL,
                    whoCardEmailEditText != null ? whoCardEmailEditText.onSaveInstanceState() : null);
            outState.putString(WHO_CARD_AVATAR_FILE, messageCenterRecyclerViewAdapter.getWhoCardAvatarFileName());
        }
        outState.putBoolean(WHO_CARD_MODE, pendingWhoCardMode);
        super.onSaveInstanceState(outState);
    }

    public boolean onFragmentExit(ApptentiveViewExitType exitType) {
        savePendingComposingMessage();
        ApptentiveViewActivity hostingActivity = (ApptentiveViewActivity) hostingActivityRef.get();
        if (hostingActivity != null) {
            DialogFragment myFrag = (DialogFragment) (hostingActivity.getSupportFragmentManager())
                    .findFragmentByTag(DIALOG_IMAGE_PREVIEW);
            if (myFrag != null) {
                myFrag.dismiss();
            }
            cleanup();
            if (exitType.equals(ApptentiveViewExitType.BACK_BUTTON)) {
                EngagementModule.engageInternal(hostingActivity, interaction,
                        MessageCenterInteraction.EVENT_NAME_CANCEL);
            } else if (exitType.equals(ApptentiveViewExitType.NOTIFICATION)) {
                EngagementModule.engageInternal(hostingActivity, interaction,
                        MessageCenterInteraction.EVENT_NAME_CANCEL, exitTypeToDataJson(exitType));
            } else {
                EngagementModule.engageInternal(hostingActivity, interaction,
                        MessageCenterInteraction.EVENT_NAME_CLOSE);
            }
        }
        return false;
    }

    public boolean cleanup() {
        clearPendingMessageCenterPushNotification();
        // Set to null, otherwise they will hold reference to the activity context
        MessageManager mgr = ApptentiveInternal.getInstance().getMessageManager();

        mgr.clearInternalOnMessagesUpdatedListeners();
        mgr.setAfterSendMessageListener(null);

        ApptentiveInternal.getInstance().getAndClearCustomData();
        ApptentiveAttachmentLoader.getInstance().clearMemoryCache();
        return true;
    }

    private void clearPendingMessageCenterPushNotification() {
        SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        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:
                    ApptentiveLog.i("Clearing pending Message Center push notification.");
                    prefs.edit().remove(Constants.PREF_KEY_PENDING_PUSH_NOTIFICATION).apply();
                    break;
                }
            } catch (JSONException e) {
                ApptentiveLog.w("Error parsing JSON from push notification.", e);
                MetricModule.sendError(e, "Parsing Push notification", pushData);
            }
        }
    }

    public void addComposingCard() {
        hideFab();
        hideProfileButton();
        messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_WHOCARD);
        messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_COMPOSING);
        messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
        messagingActionHandler.sendEmptyMessage(MSG_MESSAGE_ADD_COMPOSING);
        messagingActionHandler.sendEmptyMessage(MSG_SCROLL_TO_BOTTOM);
    }

    private boolean checkAddWhoCardIfRequired() {
        boolean whoCardDisplayedBefore = wasWhoCardAsPreviouslyDisplayed();
        boolean addedWhoCard = false;
        if (interaction.getWhoCardRequestEnabled() && interaction.getWhoCardRequired()) {
            if (!whoCardDisplayedBefore) {
                forceShowKeyboard = true;
                addWhoCard(true);
                addedWhoCard = true;
            } else {
                String savedEmail = Apptentive.getPersonEmail();
                if (TextUtils.isEmpty(savedEmail)) {
                    forceShowKeyboard = true;
                    addWhoCard(false);
                    addedWhoCard = true;
                }
            }
        }
        if (addedWhoCard) {
            JSONObject data = new JSONObject();
            try {
                data.put("required", interaction.getWhoCardRequired());
                data.put("trigger", "automatic");
            } catch (JSONException e) {
                //
            }
            EngagementModule.engageInternal(hostingActivityRef.get(), interaction,
                    MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString());
            return true;
        }
        return false;
    }

    public void addWhoCard(boolean initial) {
        hideFab();
        hideProfileButton();
        JSONObject profile = interaction.getProfile();
        if (profile != null) {
            pendingWhoCardMode = initial;
            messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_WHOCARD);
            messagingActionHandler.removeMessages(MSG_MESSAGE_ADD_COMPOSING);
            messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
            messagingActionHandler.sendMessage(
                    messagingActionHandler.obtainMessage(MSG_MESSAGE_ADD_WHOCARD, initial ? 0 : 1, 0, profile));
        }
    }

    private void addExpectationStatusIfNeeded() {
        messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
        messagingActionHandler.sendEmptyMessage(MSG_OPT_INSERT_REGULAR_STATUS);
    }

    /**
     * Call only from handler.
     */
    public void displayNewIncomingMessageItem(ApptentiveMessage message) {
        messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
        // Determine where to insert the new incoming message. It will be in front of any eidting
        // area, i.e. composing, Who Card ...
        int insertIndex = listItems.size(); // If inserted onto the end, then the list will have grown by one.

        outside_loop:
        // Starting at end of list, go back up the list to find the proper place to insert the incoming message.
        for (int i = listItems.size() - 1; i > 0; i--) {
            MessageCenterListItem item = listItems.get(i);
            switch (item.getListItemType()) {
            case MESSAGE_COMPOSER:
            case MESSAGE_CONTEXT:
            case WHO_CARD:
            case STATUS:
                insertIndex--;
                break;
            default:
                // Any other type means we are past the temporary items.
                break outside_loop;
            }
        }
        listItems.add(insertIndex, message);
        messageCenterRecyclerViewAdapter.notifyItemInserted(insertIndex);

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

    public void addAttachmentsToComposer(ImageItem... images) {
        ArrayList<ImageItem> newImages = new ArrayList<ImageItem>();
        // only add new images, and filter out duplicates
        if (images != null && images.length > 0) {
            for (ImageItem newImage : images) {
                boolean bDupFound = false;
                for (ImageItem pendingAttachment : pendingAttachments) {
                    if (newImage.originalPath.equals(pendingAttachment.originalPath)) {
                        bDupFound = true;
                        break;
                    }
                }
                if (bDupFound) {
                    continue;
                } else {
                    pendingAttachments.add(newImage);
                    newImages.add(newImage);
                }
            }
        }
        View v = messageCenterRecyclerView.getChildAt(0);
        int top = (v == null) ? 0 : v.getTop();

        if (newImages.isEmpty()) {
            return;
        }
        messageCenterRecyclerViewAdapter.addImagestoComposer(composer, newImages);
        messageCenterRecyclerViewAdapter.notifyItemChanged(listItems.size() - 1);
        int firstIndex = messageCenterRecyclerView.getFirstVisiblePosition();
        messagingActionHandler
                .sendMessage(messagingActionHandler.obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top));
    }

    public void setAttachmentsInComposer(final List<ImageItem> images) {
        messageCenterRecyclerViewAdapter.addImagestoComposer(composer, images);
        // The view will resize. Scroll it into view after a short delay to ensure the view has already resized.
        messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, 50);

    }

    public void removeImageFromComposer(final int position) {
        EngagementModule.engageInternal(hostingActivityRef.get(), interaction,
                MessageCenterInteraction.EVENT_NAME_ATTACHMENT_DELETE);
        messagingActionHandler
                .sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_ATTACHMENT, position, 0));
        messagingActionHandler.sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS);
    }

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

        try {
            if (!Util.openFileAttachment(hostingActivityRef.get(), image.originalPath, image.localCachePath,
                    image.mimeType)) {
                ApptentiveLog.d("Cannot open file attachment");
            }
        } catch (Exception e) {
            ApptentiveLog.e("Error loading attachment", e);
        }
    }

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

        try {

            FragmentTransaction ft = getFragmentManager().beginTransaction();
            Fragment prev = getFragmentManager().findFragmentByTag(DIALOG_IMAGE_PREVIEW);
            if (prev != null) {
                ft.remove(prev);
            }
            ft.addToBackStack(null);

            // Create and show the dialog.
            AttachmentPreviewDialog dialog = AttachmentPreviewDialog.newInstance(image);
            dialog.show(ft, DIALOG_IMAGE_PREVIEW);

        } catch (Exception e) {
            ApptentiveLog.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()) {
            messagingActionHandler
                    .sendMessage(messagingActionHandler.obtainMessage(MSG_MESSAGE_SENT, apptentiveMessage));
        }
    }

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

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

    @Override
    public void onComposingViewCreated(MessageComposerHolder composer, final EditText composerEditText,
            final ApptentiveImageGridView attachments) {
        this.composer = composer;
        this.composerEditText = composerEditText;

        SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        // Restore composing text editing state, such as cursor position, after rotation
        if (composingViewSavedState != null) {
            if (this.composerEditText != null) {
                this.composerEditText.onRestoreInstanceState(composingViewSavedState);
            }
            composingViewSavedState = null;
            SharedPreferences.Editor editor = prefs.edit();
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE).apply();
        }
        // 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 && this.composerEditText != null) {
                this.composerEditText.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).apply();
        }

        setAttachmentsInComposer(pendingAttachments);

        messageCenterRecyclerView.setPadding(0, 0, 0, 0);

        if (composerEditText != null) {
            composerEditText.requestFocus();
            if (forceShowKeyboard) {
                composerEditText.post(new Runnable() {
                    @Override
                    public void run() {
                        if (forceShowKeyboard) {
                            forceShowKeyboard = false;
                            Util.showSoftKeyboard(hostingActivityRef.get(), composerEditText);
                        }
                    }
                });
            }
        }
        hideFab();
        composer.setSendButtonState();
    }

    @Override
    public void onWhoCardViewCreated(final EditText nameEditText, final EditText emailEditText,
            final View viewToFocus) {
        this.whoCardNameEditText = nameEditText;
        this.whoCardEmailEditText = emailEditText;
        if (pendingWhoCardName != null) {
            nameEditText.onRestoreInstanceState(pendingWhoCardName);
            pendingWhoCardName = null;
        }
        if (pendingWhoCardEmail != null) {
            emailEditText.onRestoreInstanceState(pendingWhoCardEmail);
            pendingWhoCardEmail = null;
        }
        messageCenterRecyclerView.setPadding(0, 0, 0, 0);

        if (viewToFocus != null) {
            viewToFocus.requestFocus();
            if (forceShowKeyboard) {
                viewToFocus.post(new Runnable() {
                    @Override
                    public void run() {
                        if (forceShowKeyboard) {
                            forceShowKeyboard = false;
                            Util.showSoftKeyboard(hostingActivityRef.get(), viewToFocus);
                        }
                    }
                });
            }
        }
        hideFab();
    }

    @Override
    public void beforeComposingTextChanged(CharSequence str) {

    }

    @Override
    public void onComposingTextChanged(CharSequence str) {
    }

    @Override
    public void afterComposingTextChanged(String message) {
        composer.setSendButtonState();
    }

    @Override
    public void onCancelComposing() {
        Util.hideSoftKeyboard(hostingActivityRef.get(), getView());

        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(hostingActivityRef.get(), interaction,
                MessageCenterInteraction.EVENT_NAME_COMPOSE_CLOSE, data.toString());
        messagingActionHandler.sendMessage(messagingActionHandler.obtainMessage(MSG_REMOVE_COMPOSER));
        if (messageCenterRecyclerViewAdapter != null) {
            addExpectationStatusIfNeeded();
        }
        pendingAttachments.clear();
        composerEditText.getText().clear();
        composingViewSavedState = null;
        clearPendingComposingMessage();
        showFab();
        showProfileButton();
    }

    @Override
    public void onFinishComposing() {
        messagingActionHandler.sendEmptyMessage(MSG_REMOVE_COMPOSER);

        Util.hideSoftKeyboard(hostingActivityRef.get(), getView());
        messagingActionHandler.sendEmptyMessage(MSG_SEND_PENDING_CONTEXT_MESSAGE);
        if (!TextUtils.isEmpty(composerEditText.getText().toString().trim()) || pendingAttachments.size() > 0) {
            CompoundMessage compoundMessage = new CompoundMessage();
            compoundMessage.setBody(composerEditText.getText().toString().trim());
            compoundMessage.setRead(true);
            compoundMessage.setCustomData(ApptentiveInternal.getInstance().getAndClearCustomData());
            compoundMessage.setAssociatedImages(new ArrayList<ImageItem>(pendingAttachments));

            messagingActionHandler
                    .sendMessage(messagingActionHandler.obtainMessage(MSG_START_SENDING, compoundMessage));
            composingViewSavedState = null;
            composerEditText.getText().clear();
            pendingAttachments.clear();
            clearPendingComposingMessage();
        }
        showFab();
        showProfileButton();
    }

    @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(hostingActivityRef.get(), interaction,
                MessageCenterInteraction.EVENT_NAME_PROFILE_SUBMIT, data.toString());

        setWhoCardAsPreviouslyDisplayed();
        cleanupWhoCard();

        if (shouldOpenComposerAfterClosingWhoCard()) {
            addComposingCard();
        } else {
            showFab();
            showProfileButton();
        }
    }

    @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(hostingActivityRef.get(), interaction,
                MessageCenterInteraction.EVENT_NAME_PROFILE_CLOSE, data.toString());

        setWhoCardAsPreviouslyDisplayed();
        cleanupWhoCard();

        if (shouldOpenComposerAfterClosingWhoCard()) {
            addComposingCard();
        } else {
            showFab();
            showProfileButton();
        }
    }

    private boolean shouldOpenComposerAfterClosingWhoCard() {
        return interaction.getWhoCard().isRequire() && (recyclerViewContainsItemOfType(MESSAGE_CONTEXT)
                || recyclerViewContainsItemOfType(MESSAGE_OUTGOING));
    }

    public void cleanupWhoCard() {
        messagingActionHandler.sendEmptyMessage(MSG_MESSAGE_REMOVE_WHOCARD);
        Util.hideSoftKeyboard(hostingActivityRef.get(), getView());
        pendingWhoCardName = null;
        pendingWhoCardEmail = null;
        pendingWhoCardAvatarFile = null;
        pendingWhoCardMode = false;
        whoCardNameEditText = null;
        whoCardEmailEditText = null;
        addExpectationStatusIfNeeded();
    }

    @Override
    public void onNewMessageReceived(final CompoundMessage apptentiveMsg) {
        messagingActionHandler
                .sendMessage(messagingActionHandler.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);
        }
        showToolbarElevation(bCanScrollUp);
    }

    @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);
                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);
                startActivityForResult(chooserIntent, Constants.REQUEST_CODE_PHOTO_FROM_SYSTEM_PICKER);
            }
            imagePickerStillOpen = true;
        } catch (Exception e) {
            e.printStackTrace();
            imagePickerStillOpen = false;
            ApptentiveLog.d("can't launch image picker");
        }
    }

    private void setWhoCardAsPreviouslyDisplayed() {
        SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, true);
        editor.apply();
    }

    private boolean wasWhoCardAsPreviouslyDisplayed() {
        SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        return prefs.getBoolean(Constants.PREF_KEY_MESSAGE_CENTER_WHO_CARD_DISPLAYED_BEFORE, false);
    }

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

    public void savePendingComposingMessage() {
        Editable content = getPendingComposingContent();
        SharedPreferences prefs = ApptentiveInternal.getInstance().getSharedPrefs();
        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 pendingAttachmentsJsonArray = new JSONArray();
        // Save pending attachment
        for (ImageItem pendingAttachment : pendingAttachments) {
            pendingAttachmentsJsonArray.put(pendingAttachment.toJSON());
        }

        if (pendingAttachmentsJsonArray.length() > 0) {
            editor.putString(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS,
                    pendingAttachmentsJsonArray.toString());
        } else {
            editor.remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS);
            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 = ApptentiveInternal.getInstance().getSharedPrefs();
        prefs.edit().remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_MESSAGE)
                .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS)
                .remove(Constants.PREF_KEY_MESSAGE_CENTER_PENDING_COMPOSING_ATTACHMENTS).apply();
    }

    private Parcelable saveEditTextInstanceState() {
        if (composerEditText != null) {
            // Hide keyboard if the keyboard was up prior to rotation
            Util.hideSoftKeyboard(hostingActivityRef.get(), getView());
            return composerEditText.onSaveInstanceState();
        }
        return null;
    }

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

    public void updateMessageSentStates() {
        dateStampsSeen.clear();
        MessageCenterUtil.CompoundMessageCommonInterface lastSent = null;
        Set<String> uniqueNonce = new HashSet<String>();
        int removedItems = 0;
        ListIterator<MessageCenterListItem> listItemIterator = listItems.listIterator();
        while (listItemIterator.hasNext()) {
            int adapterMessagePosition = listItemIterator.nextIndex() - removedItems;
            MessageCenterListItem message = listItemIterator.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())) {
                    listItemIterator.remove();
                    messageCenterRecyclerViewAdapter.notifyItemRemoved(adapterMessagePosition);
                    removedItems++;
                    continue;
                }
                // Update timestamps
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) message;
                Double sentOrReceivedAt = apptentiveMessage.getCreatedAt();
                String dateStamp = createDatestamp(sentOrReceivedAt);
                if (dateStamp != null) {
                    if (dateStampsSeen.add(dateStamp)) {
                        if (apptentiveMessage.setDatestamp(dateStamp)) {
                            messageCenterRecyclerViewAdapter.notifyItemChanged(adapterMessagePosition);
                        }
                    } else {
                        if (apptentiveMessage.clearDatestamp()) {
                            messageCenterRecyclerViewAdapter.notifyItemChanged(adapterMessagePosition);
                        }
                    }
                }

                //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 int calculateFabPadding(Context context) {
        Resources res = context.getResources();
        float scale = res.getDisplayMetrics().density;
        return (int) (res.getDimension(R.dimen.apptentive_message_center_bottom_padding) * scale + 0.5f);

    }

    private void showFab() {
        messageCenterRecyclerView.setPadding(0, 0, 0, fabPaddingPixels);
        // Re-enable Fab at the beginning of the animation
        if (fab.getVisibility() != View.VISIBLE) {
            fab.setEnabled(true);
            AnimationUtil.scaleFadeIn(fab);
        }
    }

    private void hideFab() {
        // Make sure Fab is not clickable during fade-out animation
        if (fab.getVisibility() != View.GONE) {
            fab.setEnabled(false);
            AnimationUtil.scaleFadeOutGone(fab);
        }
    }

    private void showProfileButton() {
        bShowProfileMenuItem = true;
        updateMenuVisibility();
    }

    private void hideProfileButton() {
        bShowProfileMenuItem = false;
        updateMenuVisibility();
    }

    /*
     * 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) {
        listItems.clear();
        unsentMessagesCount = 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) {
                    unsentMessagesCount++;
                }

                /*
                 * Find proper location to insert into the listItems list of the listview.
                 */
                ListIterator<MessageCenterListItem> listIterator = listItems.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);
                }
            }
        }
        messagingActionHandler.sendEmptyMessage(MSG_ADD_GREETING);
    }

    @Override
    public void onClickAttachment(final int position, final ImageItem image) {
        if (Util.isMimeTypeImage(image.mimeType)) {
            if (TextUtils.isEmpty(image.originalPath)) {
                // "+" placeholder is clicked
                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 overlaid "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) {

    }

    private static class MessagingActionHandler extends Handler {

        private final WeakReference messageCenterFragmentWeakReference;

        public MessagingActionHandler(MessageCenterFragment fragment) {
            messageCenterFragmentWeakReference = new WeakReference(fragment);
        }

        public void handleMessage(Message msg) {
            MessageCenterFragment fragment = (MessageCenterFragment) messageCenterFragmentWeakReference.get();
            /* Message can be delayed. If so, make sure fragment is still available and attached to activity
             * messageCenterRecyclerViewAdapter will always be set null in onDetach(). it's a good indication if
             * fragment is attached.
             */
            if (fragment == null || fragment.messageCenterRecyclerViewAdapter == null) {
                return;
            }
            switch (msg.what) {
            case MSG_MESSAGE_ADD_WHOCARD: {
                // msg.arg1 is either WHO_CARD_MODE_INIT or WHO_CARD_MODE_EDIT
                boolean initial = msg.arg1 == 0;
                WhoCard whoCard = fragment.interaction.getWhoCard();
                whoCard.setInitial(initial);
                fragment.listItems.add(whoCard);
                fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);
                fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1);
                break;
            }
            case MSG_MESSAGE_REMOVE_WHOCARD: {
                ListIterator<MessageCenterListItem> messageIterator = fragment.listItems.listIterator();
                while (messageIterator.hasNext()) {
                    int i = messageIterator.nextIndex();
                    MessageCenterListItem next = messageIterator.next();
                    if (next.getListItemType() == WHO_CARD) {
                        messageIterator.remove();
                        fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i);
                    }
                }
                break;
            }
            case MSG_MESSAGE_ADD_COMPOSING: {
                EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction,
                        MessageCenterInteraction.EVENT_NAME_COMPOSE_OPEN);
                fragment.listItems.add(fragment.interaction.getComposer());
                fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);
                fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1);
                break;
            }
            case MSG_MESSAGE_ADD_INCOMING: {
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) msg.obj;
                fragment.displayNewIncomingMessageItem(apptentiveMessage);
                break;
            }
            case MSG_SCROLL_TO_BOTTOM: {
                fragment.messageCenterRecyclerView.setSelection(fragment.listItems.size() - 1);
                fragment.messageCenterRecyclerView.scrollToPosition(fragment.listItems.size() - 1);
                break;
            }
            case MSG_SCROLL_FROM_TOP: {
                int index = msg.arg1;
                int top = msg.arg2;
                fragment.messageCenterRecyclerView.setSelectionFromTop(index, top);
                break;
            }
            case MSG_MESSAGE_SENT: {
                // below is callback handling when receiving of message is acknowledged by server through POST response
                fragment.unsentMessagesCount--;
                ApptentiveMessage apptentiveMessage = (ApptentiveMessage) msg.obj;

                for (int i = 0; i < fragment.listItems.size(); i++) {
                    MessageCenterListItem message = fragment.listItems.get(i);
                    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());
                                fragment.messageCenterRecyclerViewAdapter.notifyItemChanged(i);
                                break;
                            }
                        }
                    }
                }
                //Update timestamp display and add status message if needed
                fragment.updateMessageSentStates();
                fragment.addExpectationStatusIfNeeded();

                // Calculate the listview offset to make sure updating sent timestamp does not push the current view port
                int firstIndex = fragment.messageCenterRecyclerView.getFirstVisiblePosition();
                View v = fragment.messageCenterRecyclerView.getChildAt(0);
                int top = (v == null) ? 0 : v.getTop();

                // If Who Card is being shown while a message is sent, make sure Who Card is still in view by scrolling to bottom
                if (fragment.recyclerViewContainsItemOfType(WHO_CARD)) {
                    sendEmptyMessageDelayed(MSG_SCROLL_TO_BOTTOM, DEFAULT_DELAYMILLIS);
                } else {
                    sendMessageDelayed(obtainMessage(MSG_SCROLL_FROM_TOP, firstIndex, top), DEFAULT_DELAYMILLIS);
                }
                break;
            }
            case MSG_START_SENDING: {
                CompoundMessage message = (CompoundMessage) msg.obj;
                fragment.listItems.add(message);
                fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);
                fragment.unsentMessagesCount++;
                fragment.setPaused(false);

                ApptentiveInternal.getInstance().getMessageManager().sendMessage(message);

                // After the message is sent, show the Who Card if it has never been seen before, and the configuration specifies it should be requested.
                if (!fragment.wasWhoCardAsPreviouslyDisplayed()
                        && fragment.interaction.getWhoCardRequestEnabled()) {
                    JSONObject data = new JSONObject();
                    try {
                        data.put("required", fragment.interaction.getWhoCardRequired());
                        data.put("trigger", "automatic");
                    } catch (JSONException e) {
                        //
                    }
                    EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction,
                            MessageCenterInteraction.EVENT_NAME_PROFILE_OPEN, data.toString());
                    fragment.forceShowKeyboard = true;
                    fragment.addWhoCard(true);
                }
                break;
            }
            case MSG_SEND_PENDING_CONTEXT_MESSAGE: {
                ContextMessage contextMessage = null;
                // If the list has a context message, get it, remove it from the list, and notify the RecyclerView to update.
                ListIterator<MessageCenterListItem> iterator = fragment.listItems.listIterator();
                while (iterator.hasNext()) {
                    int index = iterator.nextIndex();
                    MessageCenterListItem item = iterator.next();
                    if (item.getListItemType() == MESSAGE_CONTEXT) {
                        contextMessage = (ContextMessage) item;
                        iterator.remove();
                        fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(index);
                        break;
                    }
                }

                if (contextMessage != null) {
                    // Create a CompoundMessage for sending and final display
                    CompoundMessage message = new CompoundMessage();
                    message.setBody(contextMessage.getBody());
                    message.setAutomated(true);
                    message.setRead(true);

                    // Add it to the RecyclerView
                    fragment.unsentMessagesCount++;
                    fragment.listItems.add(message);
                    fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);

                    // Send it to the server
                    ApptentiveInternal.getInstance().getMessageManager().sendMessage(message);
                }
                break;
            }
            case MSG_PAUSE_SENDING: {
                if (!fragment.isPaused()) {
                    fragment.setPaused(true);
                    if (fragment.unsentMessagesCount > 0) {
                        int reason = msg.arg1;
                        Message handlerMessage = fragment.messagingActionHandler.obtainMessage(MSG_ADD_STATUS_ERROR,
                                reason, 0);
                        fragment.messagingActionHandler.sendMessage(handlerMessage);
                    }
                }
                break;
            }
            case MSG_RESUME_SENDING: {
                if (fragment.isPaused()) {
                    fragment.setPaused(false);
                    if (fragment.unsentMessagesCount > 0) {
                        fragment.messagingActionHandler.sendEmptyMessage(MSG_REMOVE_STATUS);
                    }
                }
                break;
            }
            case MSG_REMOVE_COMPOSER: {
                for (int i = 0; i < fragment.listItems.size(); i++) {
                    MessageCenterListItem item = fragment.listItems.get(i);
                    if (item.getListItemType() == MESSAGE_COMPOSER) {
                        fragment.listItems.remove(i);
                        fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i);
                    }
                }
                break;
            }
            case MSG_OPT_INSERT_REGULAR_STATUS: {
                List<MessageCenterListItem> listItems = fragment.listItems;
                // Only add status if the last item in the list is a sent message.
                if (listItems.size() > 0) {
                    MessageCenterListItem lastItem = listItems.get(listItems.size() - 1);
                    if (lastItem != null && lastItem.getListItemType() == MESSAGE_OUTGOING) {
                        ApptentiveMessage apptentiveMessage = (ApptentiveMessage) lastItem;
                        if (apptentiveMessage.isOutgoingMessage()) {
                            Double createdTime = apptentiveMessage.getCreatedAt();
                            if (createdTime != null && createdTime > Double.MIN_VALUE) {
                                MessageCenterStatus status = fragment.interaction.getRegularStatus();
                                if (status != null) {
                                    EngagementModule.engageInternal(fragment.hostingActivityRef.get(),
                                            fragment.interaction, MessageCenterInteraction.EVENT_NAME_STATUS);
                                    // Add expectation status message if the last is a sent
                                    listItems.add(status);
                                    fragment.messageCenterRecyclerViewAdapter
                                            .notifyItemInserted(listItems.size() - 1);
                                }
                            }
                        }
                    }
                }
                break;
            }
            case MSG_REMOVE_STATUS: {
                List<MessageCenterListItem> listItems = fragment.listItems;
                for (int i = 0; i < listItems.size(); i++) {
                    MessageCenterListItem item = listItems.get(i);
                    if (item.getListItemType() == STATUS) {
                        listItems.remove(i);
                        fragment.messageCenterRecyclerViewAdapter.notifyItemRemoved(i);
                    }
                }
                break;
            }
            case MSG_ADD_CONTEXT_MESSAGE: {
                ContextMessage contextMessage = (ContextMessage) msg.obj;
                fragment.listItems.add(contextMessage);
                fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);
                break;
            }
            case MSG_ADD_GREETING: {
                fragment.listItems.add(0, fragment.interaction.getGreeting());
                fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(0);
                break;
            }
            case MSG_ADD_STATUS_ERROR: {
                int reason = msg.arg1;
                MessageCenterStatus status = null;
                if (reason == MessageManager.SEND_PAUSE_REASON_NETWORK) {
                    status = fragment.interaction.getErrorStatusNetwork();
                    EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction,
                            MessageCenterInteraction.EVENT_NAME_MESSAGE_NETWORK_ERROR);
                } else if (reason == MessageManager.SEND_PAUSE_REASON_SERVER) {
                    status = fragment.interaction.getErrorStatusServer();
                    EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction,
                            MessageCenterInteraction.EVENT_NAME_MESSAGE_HTTP_ERROR);
                }
                if (status != null) {
                    EngagementModule.engageInternal(fragment.hostingActivityRef.get(), fragment.interaction,
                            MessageCenterInteraction.EVENT_NAME_STATUS);
                    fragment.listItems.add(status);
                    fragment.messageCenterRecyclerViewAdapter.notifyItemInserted(fragment.listItems.size() - 1);
                }
                break;
            }
            case MSG_REMOVE_ATTACHMENT: {
                int position = msg.arg1;
                fragment.pendingAttachments.remove(position);
                fragment.messageCenterRecyclerViewAdapter.removeImageFromComposer(fragment.composer, position);
                break;
            }
            }
        }
    }

    public boolean recyclerViewContainsItemOfType(int type) {
        for (MessageCenterListItem item : listItems) {
            if (item.getListItemType() == type) {
                return true;
            }
        }
        return false;
    }

    public void setPaused(boolean paused) {
        if (isPaused ^ paused) {
            // Invalidate any unsent messages, as these will have status and progress bars that need to change.
            for (int i = 0; i < listItems.size(); i++) {
                MessageCenterListItem item = listItems.get(i);
                if (item instanceof ApptentiveMessage) {
                    ApptentiveMessage message = (ApptentiveMessage) item;
                    if (message.isOutgoingMessage() && message.getCreatedAt() == null) {
                        messageCenterRecyclerViewAdapter.notifyItemChanged(i);
                    }
                }
            }
        }
        isPaused = paused;
    }

    public boolean isPaused() {
        return isPaused;
    }
}