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

Java tutorial

Introduction

Here is the source code for com.gsma.rcs.ri.messaging.GroupTalkView.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.RiApplication;
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.IsComposingManager.INotifyComposing;
import com.gsma.rcs.ri.messaging.chat.group.SendGroupFile;
import com.gsma.rcs.ri.messaging.filetransfer.FileTransferLogView;
import com.gsma.rcs.ri.messaging.geoloc.DisplayGeoloc;
import com.gsma.rcs.ri.messaging.geoloc.EditGeoloc;
import com.gsma.rcs.ri.utils.LogUtils;
import com.gsma.rcs.ri.utils.RcsContactUtil;
import com.gsma.rcs.ri.utils.Smileys;
import com.gsma.rcs.ri.utils.Utils;
import com.gsma.services.rcs.Geoloc;
import com.gsma.services.rcs.RcsGenericException;
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.chat.ChatLog;
import com.gsma.services.rcs.chat.ChatLog.Message;
import com.gsma.services.rcs.chat.ChatLog.Message.Content;
import com.gsma.services.rcs.chat.ChatMessage;
import com.gsma.services.rcs.chat.ChatService;
import com.gsma.services.rcs.chat.ChatServiceConfiguration;
import com.gsma.services.rcs.chat.GroupChat;
import com.gsma.services.rcs.chat.GroupChat.ParticipantStatus;
import com.gsma.services.rcs.chat.GroupChatIntent;
import com.gsma.services.rcs.chat.GroupChatListener;
import com.gsma.services.rcs.contact.ContactId;
import com.gsma.services.rcs.contact.ContactUtil;
import com.gsma.services.rcs.contact.RcsContact;
import com.gsma.services.rcs.filetransfer.FileTransfer;
import com.gsma.services.rcs.filetransfer.FileTransferLog;
import com.gsma.services.rcs.filetransfer.FileTransferService;
import com.gsma.services.rcs.filetransfer.GroupFileTransferListener;
import com.gsma.services.rcs.groupdelivery.GroupDeliveryInfo;
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.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.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Group chat view
 */
public class GroupTalkView 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;

    private final static int SELECT_GEOLOCATION = 0;

    // @formatter:off
    private static final String[] PROJ_CHAT_MSG = 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

    /**
     * Query sort order
     */
    private final static String ORDER_CHAT_MSG = HistoryLog.TIMESTAMP + " ASC";

    /**
     * Intent parameters
     */
    private final static String EXTRA_PARTICIPANTS = "participants";
    private final static String EXTRA_SUBJECT = "subject";
    private final static String EXTRA_MODE = "mode";

    private enum GroupChatMode {
        INCOMING, OUTGOING, OPEN
    }

    private static final String WHERE_CLAUSE = HistoryLog.CHAT_ID + "=?";
    private static final String LOGTAG = LogUtils.getTag(GroupTalkView.class.getSimpleName());
    private static final String OPEN_GROUPCHAT = "OPEN_GROUPCHAT";
    private static final String INTITIATE_GROUPCHAT = "INTITIATE_GROUPCHAT";

    private Handler mHandler;
    private EditText mComposeText;
    private ChatService mChatService;
    private FileTransferService mFileTransferService;
    private Uri mUriHistoryProvider;
    private IsComposingManager mComposingManager;
    private TalkCursorAdapter mAdapter;
    private ChatCursorObserver mObserver;
    private boolean mChatListnerSet;
    private String mSubject;
    private String mChatId;
    private GroupChat mGroupChat;
    private Set<ContactId> mParticipants = new HashSet<>();
    private GroupChatListener mChatListener;
    private GroupFileTransferListener mFileTransferListener;
    private boolean mFileTransferListenerSet;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat_view);
        if (!isServiceConnected(ConnectionManager.RcsServiceName.CHAT, ConnectionManager.RcsServiceName.CONTACT,
                ConnectionManager.RcsServiceName.CAPABILITY, ConnectionManager.RcsServiceName.FILE_TRANSFER)) {
            showMessageThenExit(R.string.label_service_not_available);
            return;
        }
        startMonitorServices(ConnectionManager.RcsServiceName.CHAT, ConnectionManager.RcsServiceName.CONTACT,
                ConnectionManager.RcsServiceName.CAPABILITY, ConnectionManager.RcsServiceName.FILE_TRANSFER);
        try {
            initialize();
            processIntent(getIntent());
            if (LogUtils.isActive) {
                Log.d(LOGTAG, "onCreate");
            }
        } catch (RcsServiceException e) {
            showExceptionThenExit(e);
        }
    }

    private void initialize() throws RcsServiceNotAvailableException, RcsGenericException {
        Button sendButton = (Button) findViewById(R.id.send_button);
        sendButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendText();
            }
        });
        mHandler = new Handler();
        mChatListener = new GroupChatListener() {

            @Override
            public void onMessageStatusChanged(String chatId, String mimeType, String msgId, Content.Status status,
                    Content.ReasonCode reasonCode) {
                if (LogUtils.isActive) {
                    Log.i(LOGTAG, "onMessageStatusChanged chatId=" + chatId + " mime-type=" + mimeType + " msgId="
                            + msgId + " status=" + status + " reason=" + reasonCode);
                }
            }

            // Callback called when an Is-composing event has been received
            public void onComposingEvent(String chatId, ContactId contact, boolean status) {
                // Discard event if not for current chatId
                if (!chatId.equals(mChatId)) {
                    return;
                }
                displayComposingEvent(contact, status);
            }

            @Override
            public void onParticipantStatusChanged(String chatId, ContactId contact, ParticipantStatus status) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onParticipantStatusChanged chatId=" + chatId + " contact=" + contact + " status="
                            + status);
                }
            }

            @Override
            public void onMessageGroupDeliveryInfoChanged(String chatId, ContactId contact, String mimeType,
                    String msgId, GroupDeliveryInfo.Status status, GroupDeliveryInfo.ReasonCode reasonCode) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onMessageGroupDeliveryInfoChanged chatId=" + chatId + " contact=" + contact
                            + " msgId=" + msgId + " status=" + status + " reason=" + reasonCode);
                }
            }

            @Override
            public void onStateChanged(String chatId, final GroupChat.State state,
                    GroupChat.ReasonCode reasonCode) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onStateChanged chatId=" + chatId + " state=" + state + " reason=" + reasonCode);
                }
                /* Discard event if not for current chatId */
                if (mChatId == null || !mChatId.equals(chatId)) {
                    return;
                }
                final String _reasonCode = RiApplication.sGroupChatReasonCodes[reasonCode.toInt()];
                mHandler.post(new Runnable() {
                    public void run() {
                        switch (state) {
                        case STARTED:
                            break;

                        case ABORTED:
                            showMessageThenExit(getString(R.string.label_chat_aborted, _reasonCode));
                            break;

                        case REJECTED:
                            showMessageThenExit(getString(R.string.label_chat_rejected, _reasonCode));
                            break;

                        case FAILED:
                            showMessageThenExit(getString(R.string.label_chat_failed, _reasonCode));
                            break;

                        default:
                        }
                    }
                });
            }

            @Override
            public void onDeleted(Set<String> chatIds) {
                if (LogUtils.isActive) {
                    Log.i(LOGTAG, "onDeleted chatIds=".concat(Arrays.toString(chatIds.toArray())));
                }
            }

            @Override
            public void onMessagesDeleted(String chatId, Set<String> msgIds) {
                if (LogUtils.isActive) {
                    Log.i(LOGTAG,
                            "onMessagesDeleted chatId=" + chatId + " msgIds=" + Arrays.toString(msgIds.toArray()));
                }
            }

        };
        mFileTransferListener = new GroupFileTransferListener() {
            @Override
            public void onStateChanged(String chatId, String transferId, FileTransfer.State state,
                    FileTransfer.ReasonCode reasonCode) {
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "onStateChanged chatId=" + chatId + " transferId=" + transferId + " state="
                            + state + " reason=" + reasonCode);
                }
                /* Discard event if not for current chatId */
                if (mChatId == null || !mChatId.equals(chatId)) {
                    return;
                }
                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(GroupTalkView.this, fileTransfer.getFile());
                            mFileTransferService.markFileTransferAsRead(transferId);
                        }
                    } catch (RcsPersistentStorageException | RcsServiceNotAvailableException
                            | RcsGenericException e) {
                        showException(e);
                    }
                }
            }

            @Override
            public void onDeliveryInfoChanged(String chatId, ContactId contact, String transferId,
                    GroupDeliveryInfo.Status status, GroupDeliveryInfo.ReasonCode reasonCode) {
            }

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

            @Override
            public void onDeleted(String chatId, Set<String> transferIds) {
            }

        };
        mChatService = getChatApi();
        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.getGroupChatMessageMaxLength();
        if (maxMsgLength > 0) {
            InputFilter[] filterArray = new InputFilter[1];
            filterArray[0] = new InputFilter.LengthFilter(maxMsgLength);
            mComposeText.setFilters(filterArray);
        }
        mComposingManager = new IsComposingManager(configuration.getIsComposingTimeout(), getNotifyComposing());
        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) {
            }
        });
        mAdapter = new TalkCursorAdapter(this, false, mChatService, mFileTransferService);
        ListView listView = (ListView) findViewById(android.R.id.list);
        listView.setAdapter(mAdapter);
        registerForContextMenu(listView);
    }

    private boolean processIntent(Intent intent) {
        String action = intent.getAction();
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "processIntent: " + action);
        }
        String oldChatId = mChatId;
        try {
            switch ((GroupChatMode) intent.getSerializableExtra(EXTRA_MODE)) {
            case OUTGOING:
                /* Initiate a Group Chat: Get subject */
                mSubject = intent.getStringExtra(GroupTalkView.EXTRA_SUBJECT);
                updateGroupChatViewTitle(mSubject);
                /* Get the list of participants */
                ContactUtil contactUtil = ContactUtil.getInstance(this);
                List<String> contacts = intent.getStringArrayListExtra(GroupTalkView.EXTRA_PARTICIPANTS);
                if (contacts == null || contacts.isEmpty()) {
                    showMessageThenExit(R.string.label_invalid_contacts);
                    return false;
                }
                for (String contact : contacts) {
                    mParticipants.add(contactUtil.formatContact(contact));
                }
                if (mParticipants.isEmpty()) {
                    showMessageThenExit(R.string.label_invalid_contacts);
                    return false;
                }
                return initiateGroupChat(oldChatId == null);

            case OPEN:
                /* Open an existing session from the history log */
                mChatId = intent.getStringExtra(GroupChatIntent.EXTRA_CHAT_ID);
                mGroupChat = mChatService.getGroupChat(mChatId);
                if (mGroupChat == null) {
                    if (LogUtils.isActive) {
                        Log.e(LOGTAG, "Groupchat not found for Id=".concat(mChatId));
                    }
                    showMessageThenExit(R.string.label_session_not_found);
                    return false;
                }
                ChatPendingIntentManager.getChatPendingIntentManager(this).clearNotification(mChatId);
                setCursorLoader(oldChatId == null);
                RI.sChatIdOnForeground = mChatId;
                mSubject = mGroupChat.getSubject();
                updateGroupChatViewTitle(mSubject);
                /* Set list of participants */
                mParticipants = mGroupChat.getParticipants().keySet();
                if (LogUtils.isActive) {
                    Log.i(LOGTAG, "processIntent chatId=" + mChatId + " subject='" + mSubject + "'");
                }
                return true;

            case INCOMING:
                String rxChatId = intent.getStringExtra(GroupChatIntent.EXTRA_CHAT_ID);
                if (GroupChatIntent.ACTION_NEW_GROUP_CHAT_MESSAGE.equals(action)) {
                    String rxMsgId = intent.getStringExtra(GroupChatIntent.EXTRA_MESSAGE_ID);
                    mChatService.markMessageAsRead(rxMsgId);
                }
                mChatId = rxChatId;
                mGroupChat = mChatService.getGroupChat(mChatId);
                if (mGroupChat == null) {
                    showMessageThenExit(R.string.label_session_not_found);
                    return false;
                }
                setCursorLoader(oldChatId == null);
                RI.sChatIdOnForeground = mChatId;
                ContactId contact = mGroupChat.getRemoteContact();
                mSubject = mGroupChat.getSubject();
                updateGroupChatViewTitle(mSubject);
                mParticipants = mGroupChat.getParticipants().keySet();
                /* Display accept/reject dialog */
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "New group chat for chatId " + mChatId + " state=" + mGroupChat.getState());
                }
                if (GroupChat.State.INVITED == mGroupChat.getState()) {
                    displayAcceptRejectDialog(contact);
                }
                if (LogUtils.isActive) {
                    Log.d(LOGTAG, "New group chat for chatId ".concat(mChatId));
                }
                return true;
            }

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

    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
    public void onDestroy() {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "onDestroy");
        }
        if (mGroupChat != null) {
            try {
                mGroupChat.setComposingStatus(false);
            } catch (RcsServiceException e) {
                Log.w(LOGTAG, ExceptionUtil.getFullStackTrace(e));
            }
        }
        super.onDestroy();
    }

    @Override
    protected void onResume() {
        super.onResume();
        RI.sChatIdOnForeground = mChatId;
        if (mChatId != null) {
            ChatPendingIntentManager.getChatPendingIntentManager(this).clearNotification(mChatId);
        }
        try {
            if (mChatListener != null && mChatService != null && !mChatListnerSet) {
                mChatService.addEventListener(mChatListener);
                mChatListnerSet = true;
            }
            if (mFileTransferListener != null && mFileTransferService != null && !mFileTransferListenerSet) {
                mFileTransferService.addEventListener(mFileTransferListener);
                mFileTransferListenerSet = true;
            }
        } catch (RcsServiceNotAvailableException ignore) {
        } catch (RcsServiceException 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 onPause() {
        super.onPause();
        RI.sChatIdOnForeground = null;
        try {
            if (mChatListener != null && mChatService != null && mChatListnerSet) {
                mChatService.removeEventListener(mChatListener);
                mChatListnerSet = false;
            }
            if (mFileTransferListener != null && mFileTransferService != null && mFileTransferListenerSet) {
                mFileTransferService.removeEventListener(mFileTransferListener);
                mFileTransferListenerSet = false;
            }
        } catch (RcsServiceNotAvailableException ignore) {
        } catch (RcsServiceException e) {
            Log.w(LOGTAG, ExceptionUtil.getFullStackTrace(e));
        }
    }

    @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 (mGroupChat != null && geoloc != null) {
                    mGroupChat.sendMessage(geoloc);
                }
            } catch (RcsServiceException e) {
                showExceptionThenExit(e);
            }
            break;
        }
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu_gchat_item, menu);
        menu.findItem(R.id.menu_display_content).setVisible(false);
        menu.findItem(R.id.menu_listen_content).setVisible(false);
        /* Get the list item position. */
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
        Cursor cursor = (Cursor) mAdapter.getItem(info.position);
        int providerId = cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.PROVIDER_ID));
        Direction direction = Direction.valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(Message.DIRECTION)));
        if (FileTransferLog.HISTORYLOG_MEMBER_ID == providerId) {
            String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.MIME_TYPE));
            if (Utils.isImageType(mimeType)) {
                if (Direction.OUTGOING == direction) {
                    menu.findItem(R.id.menu_display_content).setVisible(true);

                } else {
                    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 {
                    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);
                    }
                }
            }
        }
        if (Direction.OUTGOING != direction) {
            menu.findItem(R.id.menu_view_group_delivery).setVisible(false);
        }
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterContextMenuInfo info = (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_view_group_delivery:
                GroupDeliveryInfoList.startActivity(this, id);
                return true;

            case R.id.menu_delete_message:
                if (ChatLog.Message.HISTORYLOG_MEMBER_ID == providerId) {
                    mChatService.deleteMessage(id);
                } else {
                    mFileTransferService.deleteFileTransfer(id);
                }
                return true;

            case R.id.menu_view_detail:
                if (ChatLog.Message.HISTORYLOG_MEMBER_ID == providerId) {
                    ChatMessageLogView.startActivity(this, id);
                } else {
                    FileTransferLogView.startActivity(this, id);
                }
                return true;

            case R.id.menu_display_content:
                if (FileTransferLog.HISTORYLOG_MEMBER_ID == providerId) {
                    String file = cursor.getString(cursor.getColumnIndexOrThrow(HistoryLog.CONTENT));
                    Utils.showPicture(this, Uri.parse(file));
                    markFileTransferAsRead(cursor, 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 (RcsServiceException e) {
            showException(e);
        }
        return true;
    }

    private void markFileTransferAsRead(Cursor cursor, String ftId) {
        try {
            RcsService.Direction dir = RcsService.Direction
                    .valueOf(cursor.getInt(cursor.getColumnIndexOrThrow(HistoryLog.DIRECTION)));
            if (RcsService.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));
        }
    }

    /**
     * Update the view title
     *
     * @param subject the group chat subject or null
     */
    private void updateGroupChatViewTitle(String subject) {
        if (!TextUtils.isEmpty(subject)) {
            setTitle(getString(R.string.title_group_chat) + " '" + mSubject + "'");
        }
    }

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

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        if (LOADER_ID != loader.getId()) {
            return;
        }
        /*
         * 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(cursor);
        /**
         * Registering content observer for chat message and file transfer content URIs. When these
         * content URIs will change, this will notify the loader to reload its data.
         */
        if (mObserver != null && !mObserver.getLoader().equals(loader)) {
            ContentResolver resolver = getContentResolver();
            resolver.unregisterContentObserver(mObserver);
            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);
    }

    /**
     * Display notification to accept or reject invitation
     *
     * @param remote remote contact
     */
    private void displayAcceptRejectDialog(ContactId remote) {
        /* Manual accept */
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.title_group_chat);
        String from = RcsContactUtil.getInstance(this).getDisplayName(remote);
        String topic = (TextUtils.isEmpty(mSubject)) ? getString(R.string.label_no_subject) : mSubject;
        String msg = getString(R.string.label_gc_from_subject, from, topic);
        builder.setMessage(msg);
        builder.setCancelable(false);
        builder.setIcon(R.drawable.ri_notif_chat_icon);
        builder.setPositiveButton(R.string.label_accept, new android.content.DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                try {
                    /* Accept the invitation */
                    mGroupChat.openChat();
                } catch (RcsServiceException e) {
                    showExceptionThenExit(e);
                }
            }
        });
        builder.setNegativeButton(R.string.label_decline, new android.content.DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                /*
                 * Let session die by timeout. Exit activity
                 */
                finish();
            }
        });
        registerDialog(builder.show());
    }

    /**
     * get a set of contact from a set of participant info
     *
     * @param setOfParticipant a set of participant info
     * @return a set of contact
     */
    private Set<String> getSetOfParticipants(Map<ContactId, ParticipantStatus> setOfParticipant) {
        Set<String> result = new HashSet<>();
        if (setOfParticipant.size() != 0) {
            for (ContactId contact : setOfParticipant.keySet()) {
                // TODO consider status ?
                result.add(contact.toString());
            }
        }
        return result;
    }

    /**
     * Initiate the group chat and open a progress dialog waiting for the session to start
     *
     * @return True if successful
     */
    private boolean initiateGroupChat(boolean firstLoad) {
        /* Initiate the group chat session in background */
        try {
            mGroupChat = mChatService.initiateGroupChat(new HashSet<>(mParticipants), mSubject);
            mChatId = mGroupChat.getChatId();
            setCursorLoader(firstLoad);
            RI.sChatIdOnForeground = mChatId;
            return true;

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

    /**
     * Add participants to be invited in the session
     */
    private void addParticipants() {
        /* Build list of available contacts not already in the conference */
        Set<ContactId> availableParticipants = new HashSet<>();
        try {
            Set<RcsContact> contacts = getContactApi().getRcsContacts();
            for (RcsContact rcsContact : contacts) {
                ContactId contact = rcsContact.getContactId();
                if (mGroupChat.isAllowedToInviteParticipant(contact)) {
                    availableParticipants.add(contact);
                }
            }
        } catch (RcsServiceException e) {
            showException(e);
            return;
        }
        /* Check if some participants are available */
        if (availableParticipants.size() == 0) {
            showMessage(R.string.label_no_participant_found);
            return;
        }
        /* Display contacts */
        final List<String> selectedParticipants = new ArrayList<>();
        final CharSequence[] items = new CharSequence[availableParticipants.size()];
        int i = 0;
        for (ContactId contact : availableParticipants) {
            items[i++] = contact.toString();
        }
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.label_select_contacts);
        builder.setCancelable(true);
        builder.setMultiChoiceItems(items, null, new DialogInterface.OnMultiChoiceClickListener() {
            public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                String c = (String) items[which];
                if (isChecked) {
                    selectedParticipants.add(c);
                } else {
                    selectedParticipants.remove(c);
                }
            }
        });
        builder.setNegativeButton(R.string.label_cancel, null);
        builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int position) {
                /* Add new participants in the session in background */
                try {
                    int max = mGroupChat.getMaxParticipants() - 1;
                    int connected = mGroupChat.getParticipants().size();
                    int limit = max - connected;
                    if (selectedParticipants.size() > limit) {
                        showMessage(R.string.label_max_participants);
                        return;
                    }
                    Set<ContactId> contacts = new HashSet<>();
                    ContactUtil contactUtils = ContactUtil.getInstance(GroupTalkView.this);
                    for (String participant : selectedParticipants) {
                        contacts.add(contactUtils.formatContact(participant));
                    }
                    /* Add participants */
                    mGroupChat.inviteParticipants(contacts);

                } catch (RcsServiceException e) {
                    showException(e);
                }
            }
        });
        registerDialog(builder.show());
    }

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

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem menuItemParticipants = menu.findItem(R.id.menu_participants);
        MenuItem menuItemSendFile = menu.findItem(R.id.menu_send_file);
        MenuItem menuItemLeave = menu.findItem(R.id.menu_close_session);
        try {
            if (mGroupChat != null) {
                menuItemParticipants.setEnabled(mGroupChat.isAllowedToInviteParticipants());
                menuItemLeave.setEnabled(mGroupChat.isAllowedToLeave());
                FileTransferService fileTransferService = getFileTransferApi();
                menuItemSendFile.setEnabled(fileTransferService.isAllowedToTransferFileToGroupChat(mChatId));
            } else {
                menuItemParticipants.setEnabled(false);
                menuItemSendFile.setEnabled(false);
                menuItemLeave.setEnabled(false);
            }
        } catch (RcsServiceException e) {
            showException(e);
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        try {
            switch (item.getItemId()) {
            case R.id.menu_insert_smiley:
                AlertDialog alert = Smileys.showSmileyDialog(this, mComposeText, getResources(),
                        getString(R.string.menu_insert_smiley));
                registerDialog(alert);
                break;

            case R.id.menu_participants:
                alert = Utils.showList(this, getString(R.string.menu_participants),
                        getSetOfParticipants(mGroupChat.getParticipants()));
                registerDialog(alert);
                break;

            case R.id.menu_add_participant:
                addParticipants();
                break;

            case R.id.menu_quicktext:
                addQuickText();
                break;

            case R.id.menu_send_file:
                SendGroupFile.startActivity(this, mChatId);
                break;

            case R.id.menu_send_geoloc:
                getGeoLoc();
                break;

            case R.id.menu_showus_map:
                DisplayGeoloc.showContactsOnMap(this, mGroupChat.getParticipants().keySet());
                break;

            case R.id.menu_close_session:
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setTitle(R.string.title_chat_exit);
                builder.setPositiveButton(R.string.label_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        if (mGroupChat != null) {
                            try {
                                mGroupChat.leave();
                            } catch (RcsServiceException e) {
                                showExceptionThenExit(e);
                            }
                        }
                        GroupTalkView.this.finish();
                    }
                });
                builder.setNegativeButton(R.string.label_cancel, null);
                builder.setCancelable(true);
                registerDialog(builder.show());
                break;
            }

        } catch (RcsServiceException e) {
            showException(e);
        }
        return true;
    }

    /**
     * Initiate a new Group Chat
     *
     * @param ctx context
     * @param subject subject
     * @param participants list of participants
     */
    public static void initiateGroupChat(Context ctx, String subject, ArrayList<String> participants) {
        Intent intent = new Intent(ctx, GroupTalkView.class);
        intent.setAction(INTITIATE_GROUPCHAT);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.putStringArrayListExtra(GroupTalkView.EXTRA_PARTICIPANTS, participants);
        intent.putExtra(GroupTalkView.EXTRA_MODE, GroupChatMode.OUTGOING);
        intent.putExtra(GroupTalkView.EXTRA_SUBJECT, subject);
        ctx.startActivity(intent);
    }

    /**
     * Open a Group Chat
     *
     * @param ctx The context.
     * @param chatId The chat ID.
     */
    public static void openGroupChat(Context ctx, String chatId) {
        Intent intent = new Intent(ctx, GroupTalkView.class);
        intent.setAction(OPEN_GROUPCHAT);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        intent.putExtra(GroupTalkView.EXTRA_MODE, GroupChatMode.OPEN);
        intent.putExtra(GroupChatIntent.EXTRA_CHAT_ID, chatId);
        ctx.startActivity(intent);
    }

    /**
     * Forge intent to notify Group Chat message
     *
     * @param ctx The context.
     * @param newgroupChatMessage The original intent.
     * @param chatId the chat ID
     * @return intent
     */
    public static Intent forgeIntentNewMessage(Context ctx, Intent newgroupChatMessage, String chatId) {
        newgroupChatMessage.setClass(ctx, GroupTalkView.class);
        newgroupChatMessage.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        newgroupChatMessage.putExtra(GroupTalkView.EXTRA_MODE, GroupChatMode.INCOMING);
        newgroupChatMessage.putExtra(GroupChatIntent.EXTRA_CHAT_ID, chatId);
        return newgroupChatMessage;
    }

    /**
     * Forge intent to notify new Group Chat
     *
     * @param ctx The context.
     * @param invitation The original intent.
     * @return intent
     */
    public static Intent forgeIntentInvitation(Context ctx, Intent invitation) {
        invitation.setClass(ctx, GroupTalkView.class);
        invitation.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        invitation.putExtra(GroupTalkView.EXTRA_MODE, GroupChatMode.INCOMING);
        return invitation;
    }

    private ChatMessage sendMessage(String message) throws RcsServiceException {
        if (LogUtils.isActive) {
            Log.d(LOGTAG, "sendTextMessage: ".concat(message));
        }
        return mGroupChat.sendMessage(message);
    }

    private INotifyComposing getNotifyComposing() {
        return new INotifyComposing() {
            public void setTypingStatus(boolean isTyping) {
                try {
                    if (mGroupChat != null) {
                        mGroupChat.setComposingStatus(isTyping);
                        if (LogUtils.isActive) {
                            Log.d(LOGTAG, "sendIsComposingEvent ".concat(String.valueOf(isTyping)));
                        }
                    }
                } catch (RcsGenericException e) {
                    showException(e);
                }
            }
        };
    }

    private void displayComposingEvent(ContactId contact, final boolean status) {
        final String from = RcsContactUtil.getInstance(this).getDisplayName(contact);
        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 sendText() {
        String text = mComposeText.getText().toString();
        if (!TextUtils.isEmpty(text)) {
            try {
                sendMessage(text);
                mComposingManager.messageWasSent();
                mComposeText.setText(null);
            } catch (RcsServiceException e) {
                showExceptionThenExit(e);
            }
        }
    }

    private void addQuickText() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(R.string.label_select_quicktext);
        builder.setCancelable(true);
        builder.setItems(R.array.select_quicktext, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                String[] items = getResources().getStringArray(R.array.select_quicktext);
                mComposeText.append(items[which]);
            }
        });
        registerDialog(builder.show());
    }

    private void getGeoLoc() {
        // Start a new activity to send a geolocation
        startActivityForResult(new Intent(this, EditGeoloc.class), SELECT_GEOLOCATION);
    }

}