com.gsma.rcs.ri.messaging.OneToOneTalkView.java Source code

Java tutorial

Introduction

Here is the source code for com.gsma.rcs.ri.messaging.OneToOneTalkView.java

Source

/*******************************************************************************
 * Software Name : RCS IMS Stack
 * <p/>
 * Copyright (C) 2010-2016 Orange.
 * <p/>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p/>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p/>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.gsma.rcs.ri.messaging;

import com.gsma.rcs.api.connection.ConnectionManager;
import com.gsma.rcs.api.connection.utils.ExceptionUtil;
import com.gsma.rcs.api.connection.utils.RcsFragmentActivity;
import com.gsma.rcs.ri.R;
import com.gsma.rcs.ri.RI;
import com.gsma.rcs.ri.messaging.adapter.TalkCursorAdapter;
import com.gsma.rcs.ri.messaging.chat.ChatCursorObserver;
import com.gsma.rcs.ri.messaging.chat.ChatMessageLogView;
import com.gsma.rcs.ri.messaging.chat.ChatPendingIntentManager;
import com.gsma.rcs.ri.messaging.chat.IsComposingManager;
import com.gsma.rcs.ri.messaging.chat.single.SendSingleFile;
import com.gsma.rcs.ri.messaging.chat.single.SingleChatIntentService;
import com.gsma.rcs.ri.messaging.filetransfer.FileTransferIntentService;
import com.gsma.rcs.ri.messaging.filetransfer.FileTransferLogView;
import com.gsma.rcs.ri.messaging.geoloc.EditGeoloc;
import com.gsma.rcs.ri.utils.ContactUtil;
import com.gsma.rcs.ri.utils.LogUtils;
import com.gsma.rcs.ri.utils.RcsContactUtil;
import com.gsma.rcs.ri.utils.Utils;
import com.gsma.services.rcs.Geoloc;
import com.gsma.services.rcs.RcsGenericException;
import com.gsma.services.rcs.RcsPermissionDeniedException;
import com.gsma.services.rcs.RcsPersistentStorageException;
import com.gsma.services.rcs.RcsService;
import com.gsma.services.rcs.RcsService.Direction;
import com.gsma.services.rcs.RcsServiceException;
import com.gsma.services.rcs.RcsServiceNotAvailableException;
import com.gsma.services.rcs.RcsServiceNotRegisteredException;
import com.gsma.services.rcs.capability.CapabilityService;
import com.gsma.services.rcs.chat.ChatLog;
import com.gsma.services.rcs.chat.ChatService;
import com.gsma.services.rcs.chat.ChatServiceConfiguration;
import com.gsma.services.rcs.chat.OneToOneChat;
import com.gsma.services.rcs.chat.OneToOneChatIntent;
import com.gsma.services.rcs.chat.OneToOneChatListener;
import com.gsma.services.rcs.contact.ContactId;
import com.gsma.services.rcs.filetransfer.FileTransfer;
import com.gsma.services.rcs.filetransfer.FileTransferIntent;
import com.gsma.services.rcs.filetransfer.FileTransferLog;
import com.gsma.services.rcs.filetransfer.FileTransferService;
import com.gsma.services.rcs.filetransfer.OneToOneFileTransferListener;
import com.gsma.services.rcs.history.HistoryLog;
import com.gsma.services.rcs.history.HistoryUriBuilder;

import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * One to one talk view : aggregates the RCS IM messages.
 *
 * @author Philippe LEMORDANT
 */
public class OneToOneTalkView extends RcsFragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> {

    /**
     * The loader's unique ID. Loader IDs are specific to the Activity in which they reside.
     */
    private static final int LOADER_ID = 1;

    // @formatter:off
    private static final String[] PROJECTION = new String[] { HistoryLog.BASECOLUMN_ID, HistoryLog.ID,
            HistoryLog.PROVIDER_ID, HistoryLog.MIME_TYPE, HistoryLog.CONTENT, HistoryLog.TIMESTAMP,
            HistoryLog.STATUS, HistoryLog.DIRECTION, HistoryLog.CONTACT, HistoryLog.EXPIRED_DELIVERY,
            HistoryLog.FILENAME, HistoryLog.FILESIZE, HistoryLog.TRANSFERRED, HistoryLog.REASON_CODE,
            HistoryLog.READ_STATUS };
    // @formatter:on

    private final static String EXTRA_CONTACT = "contact";

    private static final String LOGTAG = LogUtils.getTag(OneToOneTalkView.class.getSimpleName());
    /**
     * Chat_id is set to contact id for one to one chat and file transfer messages.
     */
    private static final String WHERE_CLAUSE = HistoryLog.CHAT_ID + "=?";
    private final static String ORDER_ASC = HistoryLog.TIMESTAMP + " ASC";

    private static final String OPEN_TALK = "open_talk";

    private final static int SELECT_GEOLOCATION = 0;

    /**
     * The adapter that binds data to the ListView
     */
    private TalkCursorAdapter mAdapter;
    private Uri mUriHistoryProvider;
    private ContactId mContact;
    private ChatCursorObserver mObserver;
    private EditText mComposeText;
    private ChatService mChatService;
    private OneToOneChat mChat;
    private FileTransferService mFileTransferService;
    private OneToOneChatListener mChatListener;
    private OneToOneFileTransferListener mFileTransferListener;
    private Handler mHandler;
    private AlertDialog mClearUndeliveredAlertDialog;
    private DialogInterface.OnCancelListener mUndeliveredCancelListener;
    /**
     * Utility class to manage the is-composing status
     */
    private IsComposingManager mComposingManager;
    private CapabilityService mCapabilityService;
    private Context mCtx;

    // @formatter:off
    private static final Set<String> sAllowedIntentActions = new HashSet<>(
            Arrays.asList(OneToOneChatIntent.ACTION_MESSAGE_DELIVERY_EXPIRED,
                    OneToOneChatIntent.ACTION_NEW_ONE_TO_ONE_CHAT_MESSAGE,
                    FileTransferIntent.ACTION_FILE_TRANSFER_DELIVERY_EXPIRED, OPEN_TALK));
    // @formatter:on

    private DialogInterface.OnClickListener mClearUndeliveredChat;
    private DialogInterface.OnClickListener mClearUndeliveredFt;
    private boolean mChatListenerSet;
    private boolean mFileTransferListenerSet;

    /**
     * Forge intent to start XmsView activity
     *
     * @param context The context
     * @param contact The contact ID
     * @return intent
     */
    public static Intent forgeIntentToOpenConversation(Context context, ContactId contact) {
        Intent intent = new Intent(context, OneToOneTalkView.class);
        intent.setAction(OPEN_TALK);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(EXTRA_CONTACT, (Parcelable) contact);
        return intent;
    }

    /**
     * Forge intent to start OneToOneTalkView activity upon reception of a stack event
     *
     * @param ctx The context
     * @param contact The contact ID
     * @param intent intent
     * @return intent
     */
    public static Intent forgeIntentOnStackEvent(Context ctx, ContactId contact, Intent intent) {
        intent.setClass(ctx, OneToOneTalkView.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(EXTRA_CONTACT, (Parcelable) contact);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat_view);
        if (!isServiceConnected(ConnectionManager.RcsServiceName.CONTACT, ConnectionManager.RcsServiceName.CHAT,
                ConnectionManager.RcsServiceName.FILE_TRANSFER, ConnectionManager.RcsServiceName.CAPABILITY)) {
            showMessageThenExit(R.string.label_service_not_available);
            return;
        }
        startMonitorServices(ConnectionManager.RcsServiceName.CONTACT, ConnectionManager.RcsServiceName.CHAT,
                ConnectionManager.RcsServiceName.FILE_TRANSFER, ConnectionManager.RcsServiceName.CAPABILITY);
        try {
            initialize();
            processIntent(getIntent());

        } catch (RcsServiceException e) {
            showExceptionThenExit(e);
        }
    }

    private void sendText() {
        final String text = mComposeText.getText().toString();
        if (TextUtils.isEmpty(text)) {
            return;
        }
        try {
            if (mChat != null) {
                mChat.sendMessage(text);
            }
            mComposeText.setText(null);

        } catch (RcsServiceException e) {
            showExceptionThenExit(e);
        }
    }

    private void initialize() throws RcsGenericException, RcsServiceNotAvailableException {
        mCtx = this;
        /* Set send button listener */
        Button sendBtn = (Button) findViewById(R.id.send_button);
        sendBtn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                sendText();
            }
        });
        mHandler = new Handler();
        mClearUndeliveredChat = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Set<String> msgIds = SingleChatIntentService.getUndelivered(mCtx, mContact);
                if (!msgIds.isEmpty()) {
                    try {
                        if (LogUtils.isActive) {
                            Log.d(LOGTAG, "Clear delivery expiration for IDs=" + msgIds);
                        }
                        mChatService.clearMessageDeliveryExpiration(msgIds);

                    } catch (RcsServiceException e) {
                        showException(e);
                    } finally {
                        mClearUndeliveredAlertDialog = null;
                    }
                }
            }
        };
        mUndeliveredCancelListener = new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                mClearUndeliveredAlertDialog = null;
            }
        };
        mClearUndeliveredFt = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                try {
                    Set<String> transferIds = FileTransferIntentService.getUndelivered(mCtx, mContact);
                    if (!transferIds.isEmpty()) {
                        if (LogUtils.isActive) {
                            Log.d(LOGTAG, "Clear delivery expiration for IDs=" + transferIds);
                        }
                        mFileTransferService.clearFileTransferDeliveryExpiration(transferIds);
                        mClearUndeliveredAlertDialog = null;
                    }
                } catch (RcsServiceException e) {
                    showException(e);
                } finally {
                    mClearUndeliveredAlertDialog = null;
                }
            }
        };

        mChatListener = new OneToOneChatListener() {

            /* Callback called when an Is-composing event has been received */
            @Override
            public void onComposingEvent(ContactId contact, boolean status) {
                /* Discard event if not for current contact */
                if (!contact.equals(mContact)) {
                    return;
                }
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onComposingEvent contact=" + contact + " status=" + status);
                }
                displayComposingEvent(contact, status);
            }

            @Override
            public void onMessageStatusChanged(ContactId contact, String mimeType, String msgId,
                    ChatLog.Message.Content.Status status, ChatLog.Message.Content.ReasonCode reasonCode) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onMessageStatusChanged contact=" + contact + " mime-type=" + mimeType + " msgId="
                            + msgId + " status=" + status);
                }
            }

            @Override
            public void onMessagesDeleted(ContactId contact, Set<String> msgIds) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onMessagesDeleted contact=" + contact + " for IDs=" + msgIds);
                }
            }

        };
        mFileTransferListener = new OneToOneFileTransferListener() {
            @Override
            public void onStateChanged(ContactId contact, String transferId, FileTransfer.State state,
                    FileTransfer.ReasonCode reasonCode) {
                if (!contact.equals(mContact)) {
                    return;
                }
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onStateChanged contact=" + contact + " transferId=" + transferId + " state="
                            + state + " reason=" + reasonCode);
                }
                if (FileTransfer.State.TRANSFERRED == state) {
                    try {
                        FileTransfer fileTransfer = mFileTransferService.getFileTransfer(transferId);
                        if (fileTransfer == null) {
                            return;
                        }
                        if (Utils.isAudioType(fileTransfer.getMimeType())
                                && FileTransfer.Disposition.RENDER == fileTransfer.getDisposition()) {
                            Utils.playAudio(OneToOneTalkView.this, fileTransfer.getFile());
                            mFileTransferService.markFileTransferAsRead(transferId);
                        }
                    } catch (RcsPersistentStorageException | RcsServiceNotAvailableException
                            | RcsGenericException e) {
                        showException(e);
                    }
                }
            }

            @Override
            public void onProgressUpdate(ContactId contact, String transferId, long currentSize, long totalSize) {
            }

            @Override
            public void onDeleted(ContactId contact, Set<String> transferIds) {
            }
        };
        mChatService = getChatApi();
        mCapabilityService = getCapabilityApi();
        mFileTransferService = getFileTransferApi();

        HistoryUriBuilder uriBuilder = new HistoryUriBuilder(HistoryLog.CONTENT_URI);
        uriBuilder.appendProvider(ChatLog.Message.HISTORYLOG_MEMBER_ID);
        uriBuilder.appendProvider(FileTransferLog.HISTORYLOG_MEMBER_ID);
        mUriHistoryProvider = uriBuilder.build();

        mComposeText = (EditText) findViewById(R.id.userText);
        ChatServiceConfiguration configuration = mChatService.getConfiguration();
        // Set max label length
        int maxMsgLength = configuration.getOneToOneChatMessageMaxLength();
        if (maxMsgLength > 0) {
            /* Set the message composer max length */
            InputFilter[] filterArray = new InputFilter[1];
            filterArray[0] = new InputFilter.LengthFilter(maxMsgLength);
            mComposeText.setFilters(filterArray);
        }
        IsComposingManager.INotifyComposing iNotifyComposing = new IsComposingManager.INotifyComposing() {
            public void setTypingStatus(boolean isTyping) {
                try {
                    if (mChat == null) {
                        return;
                    }
                    mChat.setComposingStatus(isTyping);
                    if (LogUtils.isActive) {
                        Boolean _isTyping = isTyping;
                        Log.d(LOGTAG, "sendIsComposingEvent ".concat(_isTyping.toString()));
                    }
                } catch (RcsGenericException e) {
                    showException(e);
                }
            }
        };
        /* Instantiate the composing manager */
        mComposingManager = new IsComposingManager(configuration.getIsComposingTimeout(), iNotifyComposing);
        mComposeText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // Check if the text is not null.
                // we do not wish to consider putting the edit text back to null
                // (like when sending message), is having activity
                if (!TextUtils.isEmpty(s)) {
                    // Warn the composing manager that we have some activity
                    if (mComposingManager != null) {
                        mComposingManager.hasActivity();
                    }
                }
            }

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        /* Initialize the adapter. */
        mAdapter = new TalkCursorAdapter(this, true, mChatService, mFileTransferService);

        /* Associate the list adapter with the ListView. */
        ListView listView = (ListView) findViewById(android.R.id.list);
        listView.setDivider(null);
        listView.setAdapter(mAdapter);
        registerForContextMenu(listView);
    }

    private boolean processIntent(Intent intent) {
        String action = intent.getAction();
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "processIntent " + action);
        }
        ContactId newContact = intent.getParcelableExtra(EXTRA_CONTACT);
        if (newContact == null) {
            if (LogUtils.isActive) {
                Log.w(LOGTAG, "Cannot process intent: contact is null");
            }
            return false;
        }
        if (action == null) {
            if (LogUtils.isActive) {
                Log.w(LOGTAG, "Cannot process intent: action is null");
            }
            return false;
        }
        if (!sAllowedIntentActions.contains(action)) {
            if (LogUtils.isActive) {
                Log.w(LOGTAG, "Cannot process intent: unauthorized action " + action);
            }
            return false;
        }
        try {
            if (!newContact.equals(mContact)) {
                /* Either it is the first conversation loading or switch to another conversation */
                loadConversation(newContact);
            }
            /* Set activity title with display name */
            String displayName = RcsContactUtil.getInstance(this).getDisplayName(mContact);
            setTitle(getString(R.string.title_chat, displayName));
            switch (action) {
            case OneToOneChatIntent.ACTION_NEW_ONE_TO_ONE_CHAT_MESSAGE:
                /*
                 * Open chat to accept session if the parameter IM SESSION START is 0. Client
                 * application is not aware of the one to one chat session state nor of the IM
                 * session start mode so we call the method systematically.
                 */
                mChat.openChat();
                break;

            case OneToOneChatIntent.ACTION_MESSAGE_DELIVERY_EXPIRED:
                processUndeliveredMessages(displayName);
                break;

            case FileTransferIntent.ACTION_FILE_TRANSFER_DELIVERY_EXPIRED:
                processUndeliveredFileTransfers(displayName);
                break;
            }
            return true;

        } catch (RcsServiceException e) {
            showExceptionThenExit(e);
            return false;
        }
    }

    private void clearNotification() {
        ChatPendingIntentManager pendingIntentManager = ChatPendingIntentManager.getChatPendingIntentManager(this);
        pendingIntentManager.clearNotification(mContact.toString());
    }

    private void loadConversation(ContactId newContact)
            throws RcsServiceNotAvailableException, RcsGenericException, RcsPersistentStorageException {
        boolean firstLoad = (mContact == null);
        /* Save contact ID */
        mContact = newContact;
        clearNotification();
        /*
         * Open chat so that if the parameter IM SESSION START is 0 then the session is accepted
         * now.
         */
        mChat = mChatService.getOneToOneChat(mContact);
        setCursorLoader(firstLoad);
        RI.sChatIdOnForeground = mContact.toString();
        /* Request for capabilities ony if they are not available or expired */
        requestCapabilities(mContact);
    }

    private void setCursorLoader(boolean firstLoad) {
        if (firstLoad) {
            /*
             * Initialize the Loader with id '1' and callbacks 'mCallbacks'.
             */
            getSupportLoaderManager().initLoader(LOADER_ID, null, this);
        } else {
            /* We switched from one contact to another: reload history since */
            getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
        }
    }

    @Override
    protected void onPause() {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "--> onPause");
        }
        super.onPause();
        RI.sChatIdOnForeground = null;
        try {
            if (mChatListener != null && mChatService != null && mChatListenerSet) {
                mChatService.removeEventListener(mChatListener);
                mChatListenerSet = false;
            }
            if (mFileTransferListener != null && mFileTransferService != null && mFileTransferListenerSet) {
                mFileTransferService.removeEventListener(mFileTransferListener);
                mFileTransferListenerSet = false;
            }
        } catch (RcsServiceNotAvailableException ignore) {
        } catch (RcsGenericException e) {
            Log.w(LOGTAG, ExceptionUtil.getFullStackTrace(e));
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        /* Replace the value of intent */
        setIntent(intent);
        processIntent(intent);
    }

    @Override
    protected void onResume() {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "--> onResume");
        }
        super.onResume();
        if (mContact != null) {
            RI.sChatIdOnForeground = mContact.toString();
            clearNotification();
        }
        try {
            if (mChatListener != null && mChatService != null && !mChatListenerSet) {
                mChatService.addEventListener(mChatListener);
                mChatListenerSet = true;
            }
            if (mFileTransferListener != null && mFileTransferService != null && !mFileTransferListenerSet) {
                mFileTransferService.addEventListener(mFileTransferListener);
                mFileTransferListenerSet = true;
            }
        } catch (RcsServiceNotAvailableException ignore) {
        } catch (RcsGenericException e) {
            Log.w(LOGTAG, ExceptionUtil.getFullStackTrace(e));
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = new MenuInflater(getApplicationContext());
        inflater.inflate(R.menu.menu_1to1_talk, menu);
        return true;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode != RESULT_OK) {
            return;
        }
        switch (requestCode) {
        case SELECT_GEOLOCATION:
            Geoloc geoloc = data.getParcelableExtra(EditGeoloc.EXTRA_GEOLOC);
            try {
                if (mChat != null) {
                    mChat.sendMessage(geoloc);
                }
            } catch (RcsServiceException e) {
                showExceptionThenExit(e);
            }
            break;
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        try {
            switch (item.getItemId()) {
            case R.id.menu_send_geoloc:
                /* Start a new activity to select a geolocation */
                startActivityForResult(new Intent(this, EditGeoloc.class), SELECT_GEOLOCATION);
                break;

            case R.id.menu_send_rcs_file:
                SendSingleFile.startActivity(this, mContact);
                break;

            case R.id.menu_delete_talk:
                mFileTransferService.deleteOneToOneFileTransfers(mContact);
                mChatService.deleteOneToOneChat(mContact);
                break;
            }
        } catch (RcsServiceException e) {
            showExceptionThenExit(e);
        }
        return true;
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_1to1_talk_item, menu);
        menu.findItem(R.id.menu_resend_message).setVisible(false);
        menu.findItem(R.id.menu_display_content).setVisible(false);
        menu.findItem(R.id.menu_listen_content).setVisible(false);
        /* Get the list item position */
        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
        Cursor cursor = (Cursor) mAdapter.getItem(info.position);
        /* Adapt the contextual menu according to the selected item */
        int providerId = cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.PROVIDER_ID));
        String id = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.ID));
        Direction direction = Direction.valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.DIRECTION)));
        try {
            switch (providerId) {
            case ChatLog.Message.HISTORYLOG_MEMBER_ID:
                if (Direction.OUTGOING == direction) {
                    ChatLog.Message.Content.Status status = ChatLog.Message.Content.Status
                            .valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.STATUS)));
                    if (ChatLog.Message.Content.Status.FAILED == status) {
                        String number = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.CONTACT));
                        if (number != null) {
                            ContactId contact = ContactUtil.formatContact(number);
                            OneToOneChat chat = mChatService.getOneToOneChat(contact);
                            if (chat != null && chat.isAllowedToSendMessage()) {
                                menu.findItem(R.id.menu_resend_message).setVisible(true);
                            }
                        }
                    }
                }
                break;

            case FileTransferLog.HISTORYLOG_MEMBER_ID:
                String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.MIME_TYPE));
                FileTransfer.State state = FileTransfer.State
                        .valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.STATUS)));
                if (FileTransfer.State.FAILED == state) {
                    FileTransfer transfer = mFileTransferService.getFileTransfer(id);
                    if (transfer != null && transfer.isAllowedToResendTransfer()) {
                        menu.findItem(R.id.menu_resend_message).setVisible(true);
                    }
                } else if (Utils.isImageType(mimeType)) {
                    if (Direction.OUTGOING == direction) {
                        menu.findItem(R.id.menu_display_content).setVisible(true);

                    } else if (Direction.INCOMING == direction) {
                        Long transferred = cursor.getLong(cursor.getColumnIndexOrThrow(HistoryLog.TRANSFERRED));
                        Long size = cursor.getLong(cursor.getColumnIndexOrThrow(HistoryLog.FILESIZE));
                        if (size.equals(transferred)) {
                            menu.findItem(R.id.menu_display_content).setVisible(true);
                        }
                    }
                } else if (Utils.isAudioType(mimeType)) {
                    if (Direction.OUTGOING == direction) {
                        menu.findItem(R.id.menu_listen_content).setVisible(true);

                    } else if (Direction.INCOMING == direction) {
                        Long transferred = cursor.getLong(cursor.getColumnIndexOrThrow(HistoryLog.TRANSFERRED));
                        Long size = cursor.getLong(cursor.getColumnIndexOrThrow(HistoryLog.FILESIZE));
                        if (size.equals(transferred)) {
                            menu.findItem(R.id.menu_listen_content).setVisible(true);
                        }
                    }
                }
                break;

            default:
                throw new IllegalArgumentException("Invalid provider ID=" + providerId);
            }
        } catch (RcsServiceNotAvailableException e) {
            menu.findItem(R.id.menu_resend_message).setVisible(false);

        } catch (RcsGenericException | RcsPersistentStorageException e) {
            menu.findItem(R.id.menu_resend_message).setVisible(false);
            showException(e);
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        Cursor cursor = (Cursor) (mAdapter.getItem(info.position));
        int providerId = cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.PROVIDER_ID));
        String id = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.ID));
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "onContextItemSelected Id=".concat(id));
        }
        try {
            switch (item.getItemId()) {
            case R.id.menu_delete_message:
                switch (providerId) {
                case ChatLog.Message.HISTORYLOG_MEMBER_ID:
                    mChatService.deleteMessage(id);
                    return true;
                case FileTransferLog.HISTORYLOG_MEMBER_ID:
                    mFileTransferService.deleteFileTransfer(id);
                    return true;
                }
                break;

            case R.id.menu_resend_message:
                switch (providerId) {
                case ChatLog.Message.HISTORYLOG_MEMBER_ID:
                    OneToOneChat chat = mChatService.getOneToOneChat(mContact);
                    if (chat != null) {
                        chat.resendMessage(id);
                    }
                    return true;

                case FileTransferLog.HISTORYLOG_MEMBER_ID:
                    FileTransfer fileTransfer = mFileTransferService.getFileTransfer(id);
                    if (fileTransfer != null) {
                        fileTransfer.resendTransfer();
                    }
                    return true;
                }
                break;

            case R.id.menu_display_content:
                switch (providerId) {
                case FileTransferLog.HISTORYLOG_MEMBER_ID:
                    String file = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.CONTENT));
                    Utils.showPicture(this, Uri.parse(file));
                    markFileTransferAsRead(cursor, id);
                    return true;
                }
                break;

            case R.id.menu_view_detail:
                switch (providerId) {
                case ChatLog.Message.HISTORYLOG_MEMBER_ID:
                    ChatMessageLogView.startActivity(this, id);
                    return true;
                case FileTransferLog.HISTORYLOG_MEMBER_ID:
                    FileTransferLogView.startActivity(this, id);
                    return true;
                }
                break;

            case R.id.menu_listen_content:
                if (FileTransferLog.HISTORYLOG_MEMBER_ID == providerId) {
                    String file = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.CONTENT));
                    Utils.playAudio(this, Uri.parse(file));
                    markFileTransferAsRead(cursor, id);
                    return true;
                }
                break;
            }
            return super.onContextItemSelected(item);

        } catch (RcsGenericException | RcsPermissionDeniedException | RcsPersistentStorageException e) {
            showException(e);
            return true;

        } catch (RcsServiceNotAvailableException e) {
            Utils.displayLongToast(this, getString(R.string.label_service_not_available));
            return true;
        }
    }

    private void markFileTransferAsRead(Cursor cursor, String ftId) {
        try {
            Direction dir = Direction.valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.DIRECTION)));
            if (Direction.INCOMING == dir) {
                RcsService.ReadStatus status = RcsService.ReadStatus
                        .valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.READ_STATUS)));
                if (RcsService.ReadStatus.UNREAD == status) {
                    mFileTransferService.markFileTransferAsRead(ftId);
                    if (LogUtils.isActive) {
                        Log.d(LOGTAG, "Mark file transfer " + ftId + " as read");
                    }
                }
            }
        } catch (RcsServiceNotAvailableException e) {
            if (LogUtils.isActive) {
                Log.d(LOGTAG, "Cannot mark message as read: service not available");
            }
        } catch (RcsGenericException | RcsPersistentStorageException e) {
            Log.e(LOGTAG, ExceptionUtil.getFullStackTrace(e));
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        /* Create a new CursorLoader with the following query parameters. */
        return new CursorLoader(this, mUriHistoryProvider, PROJECTION, WHERE_CLAUSE,
                new String[] { mContact.toString() }, ORDER_ASC);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (LOADER_ID == loader.getId()) {
            /*
             * The asynchronous load is complete and the data is now available for use. Only now can
             * we associate the queried Cursor with the CursorAdapter.
             */
            mAdapter.swapCursor(data);
            /**
             * Registering content observer for XMS message content URI. When this content URI will
             * change, this will notify the loader to reload its data.
             */
            if (mObserver != null && !mObserver.getLoader().equals(loader)) {
                ContentResolver resolver = getContentResolver();
                resolver.unregisterContentObserver(mObserver);
                mObserver = null;
            }
            if (mObserver == null) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onLoadFinished: register content observer");
                }
                mObserver = new ChatCursorObserver(new Handler(), loader);
                ContentResolver resolver = getContentResolver();
                resolver.registerContentObserver(ChatLog.Message.CONTENT_URI, true, mObserver);
                resolver.registerContentObserver(FileTransferLog.CONTENT_URI, true, mObserver);
            }
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        /*
         * For whatever reason, the Loader's data is now unavailable. Remove any references to the
         * old data by replacing it with a null Cursor.
         */
        mAdapter.swapCursor(null);
    }

    private void displayComposingEvent(final ContactId contact, final boolean status) {
        final String from = RcsContactUtil.getInstance(this).getDisplayName(contact);
        // Execute on UI handler since callback is executed from service
        mHandler.post(new Runnable() {
            public void run() {
                TextView view = (TextView) findViewById(R.id.isComposingText);
                if (status) {
                    // Display is-composing notification
                    view.setText(getString(R.string.label_contact_is_composing, from));
                    view.setVisibility(View.VISIBLE);
                } else {
                    // Hide is-composing notification
                    view.setVisibility(View.GONE);
                }
            }
        });
    }

    private void processUndeliveredFileTransfers(String displayName) throws RcsGenericException,
            RcsServiceNotAvailableException, RcsPersistentStorageException, RcsPermissionDeniedException {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "processUndeliveredFileTransfers: ask");
        }
        /* Do not propose to clear undelivered if a dialog is already opened */
        if (mClearUndeliveredAlertDialog == null) {
            mClearUndeliveredAlertDialog = popUpDeliveryExpiration(this,
                    getString(R.string.title_undelivered_filetransfer),
                    getString(R.string.label_undelivered_filetransfer, displayName), mClearUndeliveredFt, null,
                    mUndeliveredCancelListener);
            registerDialog(mClearUndeliveredAlertDialog);
        }
    }

    private void processUndeliveredMessages(String displayName)
            throws RcsGenericException, RcsPersistentStorageException, RcsServiceNotAvailableException {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "processUndeliveredMessages: ask");
        }
        /* Do not propose to clear undelivered if a dialog is already opened */
        if (mClearUndeliveredAlertDialog == null) {
            mClearUndeliveredAlertDialog = popUpDeliveryExpiration(this,
                    getString(R.string.title_undelivered_message),
                    getString(R.string.label_undelivered_message, displayName), mClearUndeliveredChat, null,
                    mUndeliveredCancelListener);
            registerDialog(mClearUndeliveredAlertDialog);
        }
    }

    private AlertDialog popUpDeliveryExpiration(Context ctx, String title, String msg,
            DialogInterface.OnClickListener onPositiveClickListener,
            DialogInterface.OnClickListener onNegativeClickListener,
            DialogInterface.OnCancelListener onCancelListener) {
        AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
        builder.setMessage(msg);
        builder.setTitle(title);
        if (onNegativeClickListener != null) {
            builder.setNegativeButton(R.string.label_cancel, onNegativeClickListener);
        }
        builder.setPositiveButton(R.string.label_ok, onPositiveClickListener);
        builder.setOnCancelListener(onCancelListener);
        return builder.show();
    }

    private void requestCapabilities(ContactId contact)
            throws RcsServiceNotAvailableException, RcsGenericException {
        try {
            mCapabilityService.requestContactCapabilities(new HashSet<>(Collections.singletonList(contact)));

        } catch (RcsServiceNotRegisteredException e) {
            Log.w(LOGTAG, "Cannot request capabilities: RCS not registered!");
        }
    }

}