Java tutorial
/******************************************************************************* * 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); } }