/*******************************************************************************
* Software Name : RCS IMS Stack
*
* Copyright (C) 2010 France Telecom S.A.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.orangelabs.rcs.provider.messaging;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import com.orangelabs.rcs.core.content.MmContent;
import com.orangelabs.rcs.core.ims.service.SessionIdGenerator;
import com.orangelabs.rcs.core.ims.service.im.chat.ChatSession;
import com.orangelabs.rcs.core.ims.service.im.chat.event.User;
import com.orangelabs.rcs.core.ims.service.im.chat.imdn.ImdnDocument;
import com.orangelabs.rcs.provider.settings.RcsSettings;
import com.orangelabs.rcs.service.api.client.eventslog.EventsLogApi;
import com.orangelabs.rcs.service.api.client.messaging.GeolocMessage;
import com.orangelabs.rcs.service.api.client.messaging.GeolocPush;
import com.orangelabs.rcs.service.api.client.messaging.GroupChatInfo;
import com.orangelabs.rcs.service.api.client.messaging.InstantMessage;
import com.orangelabs.rcs.utils.PhoneUtils;
import com.orangelabs.rcs.utils.StringUtils;
import com.orangelabs.rcs.utils.logger.Logger;
/**
* Rich messaging history. This content provider removes old messages if there is no enough space.
*
* @author mhsm6403
* @author Deutsche Telekom AG
* @author yplo6403
*/
public class RichMessaging {
/**
* Current instance
*/
private static RichMessaging instance = null;
/**
* Content resolver
*/
private ContentResolver cr;
/**
* Database URI
*/
private Uri databaseUri = RichMessagingData.CONTENT_URI;
/**
* Max log entries
*/
private int maxLogEntries;
/**
* The logger
*/
private Logger logger = Logger.getLogger(this.getClass().getName());
/**
* Create instance
*
* @param ctx
* Context
*/
public static synchronized void createInstance(Context ctx) {
if (instance == null) {
instance = new RichMessaging(ctx);
}
}
/**
* Returns instance
*
* @return Instance
*/
public static RichMessaging getInstance() {
return instance;
}
/**
* Constructor
*
* @param ctx Application context
*/
private RichMessaging(Context ctx) {
super();
this.cr = ctx.getContentResolver();
this.maxLogEntries = RcsSettings.getInstance().getMaxChatLogEntriesPerContact();
}
/**
* Get list of participants into a string
*
* @param participants List of participants
* @return String (contacts are semicolon separated)
*/
private static String getParticipants(List<String> participants) {
if (participants == null)
return null;
StringBuffer result = new StringBuffer();
boolean firstiteration = true;
for (String contact : participants) {
String number = PhoneUtils.extractNumberFromUri(contact);
if (PhoneUtils.isGlobalPhoneNumber(number)) {
if (!firstiteration) {
result.append(";");
}
result.append(number);
firstiteration = false;
}
}
if (result.length() == 0)
return null;
return result.toString();
}
/**
* Get list of participants into a string
*
* @param session Chat session
* @return String (contacts are semicolon separated)
*/
private static String getParticipants(ChatSession session) {
StringBuffer participants = new StringBuffer();
if (session.isGroupChat()) {
return getParticipants(session.getParticipants().getList());
} else {
participants.append(PhoneUtils.extractNumberFromUri(session.getRemoteContact()));
return participants.toString();
}
}
/**
* Get list of participants into a string
*
* @param session Chat session
* @return list of participants
*/
private static List<String> getListParticipants(ChatSession session) {
if (session.isGroupChat()) {
return session.getParticipants().getList();
} else {
List<String> participants = new ArrayList<String>();
participants.add(PhoneUtils.extractNumberFromUri(session.getRemoteContact()));
return participants;
}
}
/**
* Get list of participants from a string
*
* @param String participants (contacts are comma separated)
* @return String[] contacts or null if
*/
private static String[] getParticipants(String participants) {
String[] res = new String[] {};
if (participants != null && participants.trim().length() > 0) {
// In groupChat, "participants" is a concatenation of all contacts' numbers, separated by ';' and terminated by a ';'
// We call split with the param limit set to 0 for empty strings to be automatically trimmed out
res = participants.split(";", 0);
}
return res;
}
/**
* Get type corresponding to a chat system event
*
* @param session Chat session
* @return Type
*/
private int getChatSystemEventType(ChatSession session) {
if (session.isGroupChat()) {
return EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE;
} else {
return EventsLogApi.TYPE_CHAT_SYSTEM_MESSAGE;
}
}
/**
* Add a new conference event
*
* @param session Chat session
* @param contact Contact
* @param state Conference state
*/
public void addConferenceEvent(ChatSession session, String contact, String state) {
int event = getEventLogValue(state);
if (event != -1) {
addEntry(EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE, session.getSessionID(), session.getContributionID(), null, contact, null, null,
null, null, 0, null, event);
}
}
/**
* Get state event log value from conference state name
*
* @param state Conference state
* @return event log value
*
*/
private int getEventLogValue(String state){
int event = -1;
if (state.equals(User.STATE_BOOTED)) {
// Contact has lost the session and may rejoin the session after
event = EventsLogApi.EVENT_DISCONNECT_CHAT;
} else if (state.equals(User.STATE_DEPARTED)) {
// Contact has left voluntary the session
event = EventsLogApi.EVENT_LEFT_CHAT;
} else if (state.equals(User.STATE_DISCONNECTED)) {
// Contact has left voluntary the session
event = EventsLogApi.EVENT_LEFT_CHAT;
} else if (state.equals(User.STATE_CONNECTED)) {
// Contact has joined the session
event = EventsLogApi.EVENT_JOINED_CHAT;
} else if (state.equals(User.STATE_BUSY)) {
// Contact is busy
event = EventsLogApi.EVENT_BUSY;
} else if (state.equals(User.STATE_PENDING)) {
// Contact is busy
event = EventsLogApi.EVENT_PENDING;
} else if (state.equals(User.STATE_DECLINED)) {
// Contact has declined the invitation
event = EventsLogApi.EVENT_DECLINED;
} else if (state.equals(User.STATE_FAILED)) {
// Any SIP error related to the contact invitation
event = EventsLogApi.EVENT_FAILED;
}
return event;
}
/**
* Add an incoming chat session
*
* @param session Chat session
*/
public void addIncomingChatSession(ChatSession session) {
// Add session entry
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
String subject = session.getSubject();
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, subject, null, null, 0, null, EventsLogApi.EVENT_INVITED,false);
// Add first message entry
InstantMessage firstMsg = session.getFirstMessage();
if (firstMsg != null) {
addIncomingChatMessage(firstMsg, session);
}
}
/**
* Add an incoming chat session (created by FT HTTP)
*
* @param session Chat session
*/
public void addIncomingChatSessionByFtHttp(ChatSession session) {
// Add session entry
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, null, EventsLogApi.EVENT_INVITED, false);
}
/**
* Add an incoming file transfer
*
* @param contact Contact
* @param chatSessionId Chat session ID which may be null if file transfer is outside of a chat session
* @param ftSessionId File transfer session ID
* @param content File content
*/
public void addIncomingFileTransfer(String contact, String chatSessionId, String ftSessionId, MmContent content) {
// Add session entry
addEntry(EventsLogApi.TYPE_INCOMING_FILE_TRANSFER, ftSessionId, chatSessionId, null, contact, null, null, content.getEncoding(),
content.getName(), content.getSize(), null, EventsLogApi.EVENT_INITIATED);
}
/**
* Add outgoing chat session
*
* @param session Chat session
*/
public void addOutgoingChatSession(ChatSession session) {
// Add session entry
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
String subject = session.getSubject();
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, subject, null, null, 0, null, EventsLogApi.EVENT_INITIATED,
false);
// Add first message entry
InstantMessage firstMsg = session.getFirstMessage();
if (firstMsg != null) { // TODO not save if subject used for HTTP file transfer
addOutgoingChatMessage(firstMsg, session);
}
// Add rejoin ID
if (session.isGroupChat()) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_CHAT_REJOIN_ID, session.getImSessionIdentity());
cr.update(databaseUri, values, "(" + RichMessagingData.KEY_CHAT_SESSION_ID + " = \"" + session.getSessionID() + "\") AND ("
+ RichMessagingData.KEY_TYPE + " =" + type + ")", null);
}
}
/**
* Add outgoing chat session (created by FT HTTP)
*
* @param session Chat session
*/
public void addOutgoingChatSessionByFtHttp(ChatSession session) {
// Add session entry
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, null, EventsLogApi.EVENT_INITIATED, false);
}
/**
* Add outgoing file transfer
*
* @param contact Contact
* @param chatSessionId Chat session ID which may be null if file transfer took place outside of a chat session
* @param sessionId Session ID
* @param filename Filename
* @param content File content
*/
public void addOutgoingFileTransfer(String contact, String chatSessionId, String ftSessionId, String fileName, MmContent content) {
// Add session entry
addEntry(EventsLogApi.TYPE_OUTGOING_FILE_TRANSFER, ftSessionId, chatSessionId, null, contact, null, fileName, content.getEncoding(),
content.getName(), content.getSize(), null, EventsLogApi.EVENT_INITIATED);
}
/**
* Add outgoing file transfer to a group
*
* @param contactList List of contacts
* @param chatSessionId Chat session ID which may be null if file transfer took place outside of a chat session
* @param sessionId Session ID
* @param filename Filename
* @param content File content
*/
public void addOutgoingGroupFileTransfer(List<String> contactList, String chatSessionId, String ftSessionId, String fileName, MmContent content) {
// Add session entry
addEntry(EventsLogApi.TYPE_OUTGOING_FILE_TRANSFER, ftSessionId, chatSessionId, null, contactList, null, fileName,
content.getEncoding(), content.getName(), content.getSize(), null, EventsLogApi.EVENT_INITIATED, false);
}
/**
* Add incoming chat message
*
* @param msg Chat message
* @param session Chat session
*/
public void addIncomingChatMessage(InstantMessage msg, ChatSession session) {
// Add message entry
int type;
if (session.isGroupChat()) {
type = EventsLogApi.TYPE_INCOMING_GROUP_CHAT_MESSAGE;
} else {
type = EventsLogApi.TYPE_INCOMING_CHAT_MESSAGE;
}
int status = EventsLogApi.STATUS_RECEIVED;
if (msg.isImdnDisplayedRequested() && RcsSettings.getInstance().isImDisplayedNotificationActivated()) {
status = EventsLogApi.STATUS_REPORT_REQUESTED;
}
String number = PhoneUtils.extractNumberFromUri(msg.getRemote());
addEntry(type, session.getSessionID(), session.getContributionID(), msg.getMessageId(), number,
msg.getDisplayName(), msg.getTextMessage(), InstantMessage.MIME_TYPE, number, msg.getTextMessage()
.getBytes().length, msg.getDate(), status);
}
/**
* Add outgoing chat message
*
* @param msg Chat message
* @param session Chat session
*/
public void addOutgoingChatMessage(InstantMessage msg, ChatSession session) {
// Add session entry
int type;
if (session.isGroupChat()) {
type = EventsLogApi.TYPE_OUTGOING_GROUP_CHAT_MESSAGE;
} else {
type = EventsLogApi.TYPE_OUTGOING_CHAT_MESSAGE;
}
String number = PhoneUtils.extractNumberFromUri(msg.getRemote());
addEntry(type, session.getSessionID(), session.getContributionID(), msg.getMessageId(), number,
msg.getDisplayName(), msg.getTextMessage(), InstantMessage.MIME_TYPE, number, msg.getTextMessage()
.getBytes().length, msg.getDate(), EventsLogApi.STATUS_SENT);
}
/**
* Add incoming geoloc message
*
* @param geoloc Geoloc message
* @param session Chat session
*/
public void addIncomingGeoloc(GeolocMessage geoloc, ChatSession session) {
// Add message entry
int type;
if (session.isGroupChat()) {
type = EventsLogApi.TYPE_INCOMING_GROUP_GEOLOC;
} else {
type = EventsLogApi.TYPE_INCOMING_GEOLOC;
}
int status = EventsLogApi.STATUS_RECEIVED;
if (geoloc.isImdnDisplayedRequested() && RcsSettings.getInstance().isImDisplayedNotificationActivated()) {
status = EventsLogApi.STATUS_REPORT_REQUESTED;
}
String geolocData = GeolocPush.formatGeolocToStr(geoloc.getGeoloc());
String number = PhoneUtils.extractNumberFromUri(geoloc.getRemote());
addEntry(type, session.getSessionID(), session.getContributionID(), geoloc.getMessageId(), number,
geoloc.getDisplayName(), geolocData, GeolocMessage.MIME_TYPE, number, geolocData.length(),
geoloc.getDate(), status);
}
/**
* Add incoming geoloc message out of a session
*
* @param geoloc Geoloc message
*/
public void addIncomingGeoloc(GeolocMessage geoloc) {
// Add message entry
int type = EventsLogApi.TYPE_INCOMING_GEOLOC;
int status = EventsLogApi.STATUS_RECEIVED;
String geolocData = GeolocPush.formatGeolocToStr(geoloc.getGeoloc());
String number = PhoneUtils.extractNumberFromUri(geoloc.getRemote());
addEntry(type, null, null, geoloc.getMessageId(), number, geoloc.getDisplayName(), geolocData,
GeolocMessage.MIME_TYPE, number, geolocData.length(), geoloc.getDate(), status);
}
/**
* Add outgoing geoloc message
*
* @param geoloc Geoloc message
* @param session Chat session
*/
public void addOutgoingGeoloc(GeolocMessage geoloc, ChatSession session) {
// Add session entry
int type;
if (session.isGroupChat()) {
type = EventsLogApi.TYPE_OUTGOING_GROUP_GEOLOC;
} else {
type = EventsLogApi.TYPE_OUTGOING_GEOLOC;
}
String geolocData = GeolocPush.formatGeolocToStr(geoloc.getGeoloc());
String number = PhoneUtils.extractNumberFromUri(geoloc.getRemote());
addEntry(type, session.getSessionID(), session.getContributionID(), geoloc.getMessageId(), number,
geoloc.getDisplayName(), geolocData, GeolocMessage.MIME_TYPE, number, geolocData.length(),
geoloc.getDate(), EventsLogApi.STATUS_SENT);
}
/**
* Add outgoing geoloc message
*
* @param geoloc Geoloc message
*/
public void addOutgoingGeoloc(GeolocMessage geoloc) {
// Add session entry
int type = EventsLogApi.TYPE_OUTGOING_GEOLOC;
String geolocData = GeolocPush.formatGeolocToStr(geoloc.getGeoloc());
String number = PhoneUtils.extractNumberFromUri(geoloc.getRemote());
addEntry(type, null, null, geoloc.getMessageId(), number, geoloc.getDisplayName(), geolocData,
GeolocMessage.MIME_TYPE, number, geolocData.length(), geoloc.getDate(), EventsLogApi.STATUS_SENT);
}
/**
* Set the delivery status of a chat message
*
* @param msgId Message ID
* @param status Status
* @param contact contact who notified status
*/
public void setChatMessageDeliveryStatus(String msgId, String status, String contact) {
contact = PhoneUtils.extractNumberFromUri(contact);
if (status.equalsIgnoreCase(ImdnDocument.DELIVERY_STATUS_DISPLAYED)) {
setChatMessageDeliveryStatus(msgId, EventsLogApi.STATUS_DISPLAYED, contact);
} else if (status.equalsIgnoreCase(ImdnDocument.DELIVERY_STATUS_DELIVERED)) {
setChatMessageDeliveryStatus(msgId, EventsLogApi.STATUS_DELIVERED, contact);
} else if ((status.equalsIgnoreCase(ImdnDocument.DELIVERY_STATUS_ERROR)) || (status.equalsIgnoreCase(ImdnDocument.DELIVERY_STATUS_FAILED))
|| (status.equalsIgnoreCase(ImdnDocument.DELIVERY_STATUS_FORBIDDEN))) {
setChatMessageDeliveryStatus(msgId, EventsLogApi.STATUS_FAILED);
}
}
/**
* A delivery report "displayed" is requested for a given chat message
*
* @param msgId Message ID
*/
public void setChatMessageDeliveryRequested(String msgId) {
setChatMessageDeliveryStatus(msgId, EventsLogApi.STATUS_REPORT_REQUESTED);
}
/**
* Get set of contacts whom chat message is delivered
*
* @param msgId the chat message ID
* @return set of contacts
*/
public Set<String> getContactsChatDelivered(String msgId) {
return getContactsChat(msgId, RichMessagingData.KEY_IMDN_DELIVERED_LIST);
}
/**
* Get set of contacts who displayed the chat message
*
* @param msgId the chat message ID
* @return set of contacts
*/
public Set<String> getContactsChatDisplayed(String msgId) {
return getContactsChat(msgId, RichMessagingData.KEY_IMDN_DISPLAYED_LIST);
}
/**
* Get set of contacts for chat message
*
* @param msgId the chat message ID
* @param columnName the column name to query
* @return a set of contacts
*/
private Set<String> getContactsChat(final String msgId, final String columnName) {
Set<String> contactSet = new HashSet<String>();
Cursor cursor = cr.query(databaseUri, new String[] { columnName }, RichMessagingData.KEY_MESSAGE_ID + " =\'" + msgId + "\'", null, null);
if (cursor.moveToFirst()) {
String contactList = cursor.getString(0);
if (contactList != null) {
String[] separated = contactList.split(";");
for (String contact : separated) {
contactSet.add(contact);
}
}
}
cursor.close();
return contactSet;
}
/**
* Set the delivery status of a chat message
*
* @param msgId the chat Message ID
* @param status Status
*/
private void setChatMessageDeliveryStatus(String msgId, int status) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_STATUS, status);
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = \'" + msgId + "\'", null);
}
/**
* Set the delivery status and list of contacts for a chat message
*
* @param msgId message Id
* @param status delivery status
* @param contacts set of contacts
* @param column column to update with contact list (i.e. a string with ";" delimiter)
*/
private void setChatMessageDeliveryStatus(String msgId, int status, final Set<String> contacts, String column) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_STATUS, status);
// Build the list of contacts in a string with separator ";"
String contactList = StringUtils.join(contacts, ";");
values.put(column, contactList);
// Update
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = \'" + msgId + "\'", null);
}
/**
* Set the delivery status of a chat message
*
* @param msgId Message ID
* @param status Status
* @param contact the remote contact
*/
private void setChatMessageDeliveryStatus(String msgId, int status, String contact) {
if (contact == null) {
if (logger.isActivated())
logger.warn("Contact is missing for messageId " + msgId);
setChatMessageDeliveryStatus(msgId, status);
return;
}
// Update the contacts in the IMDN displayed and delivered columns
switch (status) {
case EventsLogApi.STATUS_DISPLAYED: {
// Query set of contact who displayed chat message
Set<String> contacts = getContactsChatDisplayed(msgId);
// Add current contact to set
contacts.add(contact);
// Update
setChatMessageDeliveryStatus(msgId, status, contacts, RichMessagingData.KEY_IMDN_DISPLAYED_LIST);
}
break;
case EventsLogApi.STATUS_DELIVERED: {
// Query set of contact whom chat message is delivered
Set<String> contacts = getContactsChatDelivered(msgId);
// Add current contact to set
contacts.add(contact);
// Update
setChatMessageDeliveryStatus(msgId, status, contacts, RichMessagingData.KEY_IMDN_DELIVERED_LIST);
}
break;
default:
setChatMessageDeliveryStatus(msgId, status);
}
}
/**
* Add chat session termination
*
* @param session Chat session
*/
public void addChatSessionTermination(ChatSession session) {
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
int type = getChatSystemEventType(session);
List<String> participants = RichMessaging.getListParticipants(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, new Date(),
EventsLogApi.STATUS_TERMINATED, false);
}
/**
* Add chat session termination by end user
*
* @param session Chat session
*/
public void addChatSessionTerminationByUser(ChatSession session) {
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, new Date(),
EventsLogApi.STATUS_TERMINATED_BY_USER, false);
}
/**
* Add chat session termination by remote
*
* @param session Chat session
*/
public void addChatSessionTerminationByRemote(ChatSession session) {
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, new Date(),
EventsLogApi.STATUS_TERMINATED_BY_REMOTE, false);
}
/**
* Add a chat session error
*
* @param session Chat session
*/
public void addChatSessionError(ChatSession session) {
String sessionId = session.getSessionID();
String chatId = session.getContributionID();
List<String> participants = RichMessaging.getListParticipants(session);
int type = getChatSystemEventType(session);
addEntry(type, sessionId, chatId, null, participants, null, null, null, null, 0, new Date(), EventsLogApi.STATUS_FAILED,
false);
}
/**
* Mark a chat session as started
*
* @param session Chat session
*/
public void markChatSessionStarted(ChatSession session) {
int type = getChatSystemEventType(session);
String participants = RichMessaging.getParticipants(session);
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_CHAT_REJOIN_ID, session.getImSessionIdentity());
values.put(RichMessagingData.KEY_CONTACT, participants);
cr.update(databaseUri, values, "(" + RichMessagingData.KEY_CHAT_SESSION_ID + " = \"" + session.getSessionID() + "\") AND ("
+ RichMessagingData.KEY_TYPE + " =" + type + ")", null);
}
/**
* Mark a chat message as failed
*
* @param msgId Message ID
*/
public void markChatMessageFailed(String msgId) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_STATUS, EventsLogApi.STATUS_FAILED);
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId + "\"", null);
}
/**
* Mark a chat message as read or not
*
* @param msgId Message ID
* @param isRead Read flag
*/
public void markChatMessageAsRead(String msgId, boolean isRead) {
ContentValues values = new ContentValues();
if (isRead) {
values.put(RichMessagingData.KEY_STATUS, EventsLogApi.STATUS_DISPLAYED);
} else {
values.put(RichMessagingData.KEY_STATUS, EventsLogApi.STATUS_RECEIVED);
}
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId + "\"", null);
}
/**
* Add a spam message
*
* @param msg Chat message
*/
public void addSpamMessage(InstantMessage msg) {
// TODO: 2 queries may be avoided
String id = SessionIdGenerator.getNewId();
String number = PhoneUtils.extractNumberFromUri(msg.getRemote());
addEntry(EventsLogApi.TYPE_INCOMING_CHAT_MESSAGE, id, id, msg.getMessageId(), number, msg.getDisplayName(),
msg.getTextMessage(), InstantMessage.MIME_TYPE, number, msg.getTextMessage().getBytes().length,
msg.getDate(), EventsLogApi.STATUS_RECEIVED);
markChatMessageAsSpam(msg.getMessageId(), true);
}
/**
* Add incoming chat message
*
* @param msg Chat message
* @param chatId Chat ID
*/
public void addIncomingChatMessage(InstantMessage msg, String chatId) {
int status = EventsLogApi.STATUS_RECEIVED;
if (msg.isImdnDisplayedRequested() && RcsSettings.getInstance().isImDisplayedNotificationActivated()) {
status = EventsLogApi.STATUS_REPORT_REQUESTED;
}
String number = PhoneUtils.extractNumberFromUri(msg.getRemote());
addEntry(EventsLogApi.TYPE_INCOMING_CHAT_MESSAGE, SessionIdGenerator.getNewId(), chatId, msg.getMessageId(),
number, msg.getDisplayName(), msg.getTextMessage(), InstantMessage.MIME_TYPE, msg.getRemote(), msg
.getTextMessage().getBytes().length, msg.getDate(), status);
}
/**
* Mark a chat message as spam or not
*
* @param msgId Message ID
* @param isSpam Spam flag
*/
public void markChatMessageAsSpam(String msgId, boolean isSpam) {
ContentValues values = new ContentValues();
if (isSpam) {
values.put(RichMessagingData.KEY_IS_SPAM, EventsLogApi.MESSAGE_IS_SPAM);
} else {
values.put(RichMessagingData.KEY_IS_SPAM, EventsLogApi.MESSAGE_IS_NOT_SPAM);
}
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId + "\"" + " AND " + RichMessagingData.KEY_TYPE + " = "
+ EventsLogApi.TYPE_INCOMING_CHAT_MESSAGE, null);
}
/**
* Delete all spam messages
*/
public void deleteAllSpams() {
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_ID }, RichMessagingData.KEY_IS_SPAM + " = \""
+ EventsLogApi.MESSAGE_IS_SPAM + "\"", null, null);
while (c.moveToNext()) {
long rowId = c.getLong(0);
deleteEntry(rowId);
}
c.close();
}
/**
* Delete spam message
*
* @param Message ID
*/
public void deleteSpamMessage(String msgId) {
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_ID }, RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId + "\""
+ " AND " + RichMessagingData.KEY_IS_SPAM + " = \"" + EventsLogApi.MESSAGE_IS_SPAM + "\"", null, null);
if (c != null) {
if (c.moveToFirst()) {
long rowId = c.getLong(0);
if (logger.isActivated()) {
logger.debug("Deleting spam message { msgId: " + msgId + " }");
}
deleteEntry(rowId);
}
c.close();
}
}
/**
* Clear spam messages for a given contact
*
* @param contact Contact
*/
public void clearSpamMessages(String contact) {
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_CONTACT + "= \"" + contact + "\"" + " AND " + RichMessagingData.KEY_IS_SPAM
+ " = \"" + EventsLogApi.MESSAGE_IS_SPAM + "\"", null);
if (logger.isActivated()) {
logger.debug("Clear spam messages of contact " + contact + ": deleted rows =" + deletedRows);
}
}
/**
* Add a new entry (chat event, chat message or file transfer)
*
* @param type Type of entry
* @param sessionId Session ID of a chat session or a file transfer session
* @param chatId Chat ID of a chat session
* @param messageId Message ID of a chat message
* @param contact Contact phone number
* @param displayName the display name of the remote contact
* @param data Content of the entry (an URI for FT or a simple text for IM)
* @param mimeType MIME type for a file transfer
* @param name Name of the transfered file
* @param size Size of the transfered file
* @param status Status of the entry
* @return URI of the new entry
*/
private Uri addEntry(int type, String sessionId, String chatId, String messageId, String contact, String displayName, String data, String mimeType, String name,
long size, Date date, int status) {
contact = PhoneUtils.extractNumberFromUri(contact);
List<String> participants = new ArrayList<String>();
participants.add(contact);
return addEntry(type, sessionId, chatId, messageId, participants, displayName, data, mimeType, name, size, date, status,
false);
}
/**
* Add a new entry (chat event, chat message or file transfer)
*
* @param type Type of entry
* @param sessionId Session ID of a chat session or a file transfer session
* @param chatId Chat ID of a chat session
* @param messageId Message ID of a chat message
* @param contacts List of contacts
* @param displayName the display name of the remote contact
* @param data Content of the entry (an URI for FT or a simple text for IM)
* @param mimeType MIME type for a file transfer
* @param name Name of the transfered file
* @param size Size of the transfered file
* @param status Status of the entry
* @param rejectGC if true next GC invitation will be rejected
* @return URI of the new entry
*
*/
private Uri addEntry(int type, String sessionId, String chatId, String messageId, List<String> contacts, String displayName, String data, String mimeType,
String name, long size, Date date, int status, boolean rejectGC ) {
String participants = getParticipants(contacts);
if (logger.isActivated()) {
logger.debug("Add new entry: type=" + type + ", sessionID=" + sessionId + ", chatID=" + chatId + ", messageID=" + messageId
+ ", contact=" + participants + ", display=" + displayName + ", MIME=" + mimeType + ", status=" + status);
}
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_TYPE, type);
values.put(RichMessagingData.KEY_CHAT_SESSION_ID, sessionId);
values.put(RichMessagingData.KEY_CHAT_ID, chatId);
values.put(RichMessagingData.KEY_MESSAGE_ID, messageId);
values.put(RichMessagingData.KEY_CONTACT, participants);
values.put(RichMessagingData.KEY_DISPLAY_NAME, displayName);
values.put(RichMessagingData.KEY_MIME_TYPE, mimeType);
values.put(RichMessagingData.KEY_TOTAL_SIZE, size);
values.put(RichMessagingData.KEY_NAME, name);
values.put(RichMessagingData.KEY_DATA, data);
values.put(RichMessagingData.KEY_STATUS, status);
values.put(RichMessagingData.KEY_NUMBER_MESSAGES, recycler(participants) + 1);
values.put(RichMessagingData.KEY_IS_SPAM, EventsLogApi.MESSAGE_IS_NOT_SPAM);
values.put(RichMessagingData.KEY_REJECT_GC, rejectGC ? 1 : 0);
if (date == null) {
values.put(RichMessagingData.KEY_TIMESTAMP, Calendar.getInstance().getTimeInMillis());
} else {
values.put(RichMessagingData.KEY_TIMESTAMP, date.getTime());
}
Uri uri = cr.insert(databaseUri, values);
return uri;
}
/**
* Manage the max size of the history for a given contact
*
* @param contact Contact
* @return History size
*/
private int recycler(String contact) {
// Get first and last message dates for the contact
Cursor extrem = cr.query(databaseUri, new String[] { "min(" + RichMessagingData.KEY_TIMESTAMP + ")",
"max(" + RichMessagingData.KEY_TIMESTAMP + ")" }, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'", null, null);
long minDate = -1, maxDate = -1;
if (extrem.moveToFirst()) {
minDate = extrem.getLong(0);
maxDate = extrem.getLong(1);
}
extrem.close();
if (logger.isActivated()) {
logger.debug("Recycler : minDate = " + minDate + " maxDate " + maxDate);
}
// If no entry for this contact return 0
if (minDate == -1 && maxDate == -1) {
return 0;
}
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_NUMBER_MESSAGES, RichMessagingData.KEY_CHAT_SESSION_ID,
RichMessagingData.KEY_TIMESTAMP }, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'" + " AND ("
+ RichMessagingData.KEY_TIMESTAMP + " = " + minDate + " OR " + RichMessagingData.KEY_TIMESTAMP + " = " + maxDate + ")", null,
RichMessagingData.KEY_TIMESTAMP + " ASC");
int numberOfMessagesForContact = 0;
long dateForLastMessage = 0;
if (c.moveToLast()) {
numberOfMessagesForContact = c.getInt(0);
if (logger.isActivated()) {
logger.debug("Recycler : number of messages for this contact = " + numberOfMessagesForContact);
}
if (numberOfMessagesForContact < maxLogEntries) {
// Enough place for another message... do nothing return
if (logger.isActivated()) {
logger.debug("Recycler : Enough place for another message, do nothing return");
}
c.close();
return numberOfMessagesForContact;
}
if (logger.isActivated()) {
logger.debug("Recycler : Not enough place for another message, we will have to remove something");
}
// Not enough place for another message... we will have to remove something
dateForLastMessage = c.getLong(2);
if (logger.isActivated()) {
logger.debug("Recycler : dateForLastMessage =" + new Date(dateForLastMessage).toString() + " [" + dateForLastMessage + "]");
}
}
int removedMessages = 0;
if (c.moveToFirst()) {
// Remove the first message and all the associated messages from its session
String sessionId = c.getString(1);
long firstDate = c.getLong(2);
if (logger.isActivated()) {
logger.debug("Recycler : deleting entries for (the first) sessionID : " + sessionId + " for the date : "
+ new Date(firstDate).toString() + " [" + firstDate + "]");
}
removedMessages = cr.delete(databaseUri, RichMessagingData.KEY_CHAT_SESSION_ID + " = \'" + sessionId + "\'", null);
if (logger.isActivated()) {
logger.debug("Recycler : messages removed : " + removedMessages);
}
// We also will have to set the new number of message after removing, for the last entry
if (logger.isActivated()) {
logger.debug("Recycler : set the new number of messages after removing...");
}
ContentValues values = new ContentValues();
numberOfMessagesForContact -= removedMessages;
if (logger.isActivated()) {
logger.debug("Recycler : new number of message after deletion : " + numberOfMessagesForContact);
}
values.put(RichMessagingData.KEY_NUMBER_MESSAGES, numberOfMessagesForContact);
int updatedRows = cr.update(databaseUri, values, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\' AND "
+ RichMessagingData.KEY_TIMESTAMP + " = " + dateForLastMessage, null);
if (logger.isActivated()) {
logger.debug("Recycler : updated rows for the contact (must be 1) : " + updatedRows);
}
}
c.close();
return numberOfMessagesForContact;
}
/**
* Get set of contacts who displayed file
*
* @param sessionId session ID
* @return set of contacts
*/
public Set<String> getContactsFileTransferDisplayed(String sessionId) {
return getContactsFileTransfer(sessionId, RichMessagingData.KEY_IMDN_DISPLAYED_LIST);
}
/**
* Get set of contacts whom file is delivered
*
* @param sessionId session ID
* @return set of contacts
*/
public Set<String> getContactsFileTransferDelivered(String sessionId) {
return getContactsFileTransfer(sessionId, RichMessagingData.KEY_IMDN_DELIVERED_LIST);
}
/**
* Get set of contacts for file transfer
*
* @param sessionId the session ID
* @param columnName the column to query
* @return set of contacts
*/
private Set<String> getContactsFileTransfer(String sessionId, String columnName) {
Set<String> contactSet = new HashSet<String>();
Cursor cursor = cr.query(databaseUri, new String[] { columnName }, RichMessagingData.KEY_CHAT_SESSION_ID + " =\'" + sessionId + "\'", null,
null);
if (cursor.moveToFirst()) {
String contactList = cursor.getString(0);
if (contactList != null) {
String[] separated = contactList.split(";");
for (String contact : separated) {
contactSet.add(contact);
}
}
}
cursor.close();
return contactSet;
}
/**
* Update status and contact list for a file transfer
*
* @param sessionId
* @param status
* @param contacts set of contacts
* @param column the column to update with set of contacts
*/
private void updateFileTransferStatus(String sessionId, int status, final Set<String> contacts, String column) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_STATUS, status);
// Build the list of contacts in a string with separator ";"
String contactList = StringUtils.join(contacts, ";");
values.put(column, contactList);
// Update
cr.update(databaseUri, values, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null);
}
/**
* Update file transfer status
*
* @param sessionId Session Id
* @param status New status
* @param contact the contact
*/
public void updateFileTransferStatus(String sessionId, int status, String contact) {
if (contact == null) {
updateFileTransferStatus(sessionId, status);
return;
}
// Update the contacts in the IMDN displayed and delivered columns
switch (status) {
case EventsLogApi.STATUS_DISPLAYED: {
// Query set of contacts who displayed FT
Set<String> contacts = getContactsFileTransferDisplayed(sessionId);
// Add current contact to that set
contacts.add(contact);
// Update
updateFileTransferStatus(sessionId, status, contacts, RichMessagingData.KEY_IMDN_DISPLAYED_LIST);
break;
}
case EventsLogApi.STATUS_DELIVERED: {
// Query set of contacts whom FT was delivered
Set<String> contacts = getContactsFileTransferDelivered(sessionId);
// Add current contact to that set
contacts.add(contact);
// Update
updateFileTransferStatus(sessionId, status, contacts, RichMessagingData.KEY_IMDN_DELIVERED_LIST);
break;
}
default:
updateFileTransferStatus(sessionId, status);
}
}
/**
* Update file transfer status
*
* @param sessionId Session Id
* @param status New status
*/
private void updateFileTransferStatus(String sessionId, int status) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_STATUS, status);
cr.update(databaseUri, values, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null);
}
/**
* Update file transfer ChatId
*
* @param sessionId Session Id
* @param chatId chat Id
* @param msgId msgId of the corresponding chat
*/
public void updateFileTransferChatId(String sessionId, String chatId, String msgId) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_CHAT_ID, chatId);
values.put(RichMessagingData.KEY_MESSAGE_ID, msgId);
cr.update(databaseUri, values, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null);
}
/**
* Get Message info from msgId
*
* @param msgId Message Id
* @return message info
*/
public MessageInfo getMessageInfo(String msgId) {
if (logger.isActivated()) {
logger.debug("RichMessaging getMessageInfo(" + msgId + ")");
}
MessageInfo result = null;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_CONTACT, RichMessagingData.KEY_TYPE,
RichMessagingData.KEY_CHAT_SESSION_ID, RichMessagingData.KEY_STATUS}, RichMessagingData.KEY_MESSAGE_ID + "='" + msgId + "'", null,
RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
result = new MessageInfo(msgId, cursor.getString(0), cursor.getInt(1), cursor.getString(2), cursor.getInt(3));
}
cursor.close();
return result;
}
/**
* Update file transfer download progress
*
* @param sessionId Session Id
* @param size Downloaded size
* @param totalSize Total size to download
*/
public void updateFileTransferProgress(String sessionId, long size, long totalSize) {
ContentValues values = new ContentValues();
Cursor cursor = cr.query(RichMessagingData.CONTENT_URI, new String[] { RichMessagingData.KEY_SIZE }, RichMessagingData.KEY_CHAT_SESSION_ID
+ "='" + sessionId + "'", null, null);
if (cursor.moveToFirst()) {
long downloadedSize = cursor.getLong(cursor.getColumnIndexOrThrow(RichMessagingData.KEY_SIZE));
if ((size >= downloadedSize + totalSize / 10) || size == totalSize) {
// Update size if we have done at least 10 more percent from total size since last update
// Or if we are at the end of the download (ensure we update when transfer is finished)
// This is to avoid too much updates, as the ui refreshes each time
values.put(RichMessagingData.KEY_SIZE, size);
values.put(RichMessagingData.KEY_STATUS, EventsLogApi.STATUS_IN_PROGRESS);
cr.update(databaseUri, values, RichMessagingData.KEY_MESSAGE_ID + " = " + sessionId, null);
}
}
cursor.close();
}
/**
* Update file transfer URL
*
* @param sessionId Session Id
* @param url File URL
*/
public void updateFileTransferUrl(String sessionId, String url) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_DATA, url);
values.put(RichMessagingData.KEY_STATUS, EventsLogApi.STATUS_TERMINATED);
cr.update(databaseUri, values, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null);
}
/**
* Delete a file transfer session
*
* @param sessionId Session ID
* @param contact Contact
*/
public void deleteFileTransferSession(String sessionId, String contact) {
// Count entries to be deleted
Cursor count = cr.query(databaseUri, null, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null, null);
int toBeDeletedRows = count.getCount();
if (logger.isActivated()) {
logger.debug("Delete " + toBeDeletedRows + " rows");
}
count.close();
if (toBeDeletedRows == 0) {
return;
}
// Manage recycling
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_TIMESTAMP, RichMessagingData.KEY_NUMBER_MESSAGES,
RichMessagingData.KEY_MESSAGE_ID }, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'", null, RichMessagingData.KEY_TIMESTAMP
+ " DESC");
if (c.moveToFirst()) {
long maxDate = c.getLong(0);
int numberForLast = c.getInt(1);
String lastSessionId = c.getString(2);
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_NUMBER_MESSAGES, numberForLast - toBeDeletedRows);
// If last entry for this contact equals to this file transfer message
if (sessionId.equals(lastSessionId)) {
// Update the previous one
if (c.moveToNext()) {
maxDate = c.getLong(0);
}
}
/*
* TODO : If no more message exists after deleting this one for this contact, the update is useless because it will be
* made on the message to be deleted.
*/
int updatedRows = cr.update(databaseUri, values, RichMessagingData.KEY_TIMESTAMP + " = " + maxDate + " AND "
+ RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'", null);
if (logger.isActivated()) {
logger.debug("DeleteFileTransfer : recycling updated rows (should be 1) : " + updatedRows);
}
}
c.close();
/* Delete entry */
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_MESSAGE_ID + " = " + sessionId, null);
if (logger.isActivated()) {
logger.debug("DeleteFileTransfer : deleted rows (should be 1) : " + deletedRows);
}
}
/**
* Delete history associated to a contact
*
* @param contact Contact
*/
public void deleteContactHistory(String contact) {
String excludeGroupChat = RichMessagingData.KEY_TYPE + "<>" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + " AND "
+ RichMessagingData.KEY_TYPE + "<>" + EventsLogApi.TYPE_INCOMING_GROUP_CHAT_MESSAGE + " AND " + RichMessagingData.KEY_TYPE + "<>"
+ EventsLogApi.TYPE_OUTGOING_GROUP_CHAT_MESSAGE;
// Delete entries
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\' AND " + excludeGroupChat, null);
if (logger.isActivated()) {
logger.debug("DeleteSession: deleted rows : " + deletedRows);
}
}
/**
* Delete a group chat conversation
*
* @param chatId chat ID
*/
public void deleteGroupChatConversation(String chatId) {
// Delete entry
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_CHAT_ID + "=\"" + chatId + "\"", null);
if (logger.isActivated()) {
logger.debug("Delete group chat conversation: " + deletedRows + " rows deleted");
}
}
/**
* Delete a chat session. If file transfer is enclosed in the session, it will also be deleted.
*
* @param sessionId Session ID
*/
public void deleteChatSession(String sessionId) {
// Count entries to be deleted
Cursor count = cr.query(databaseUri, null, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null, null);
int toBeDeletedRows = count.getCount();
String contact = null;
boolean isGroupChat = false;
if (count.moveToFirst()) {
contact = count.getString(count.getColumnIndex(RichMessagingData.KEY_CONTACT));
int type = count.getInt(count.getColumnIndex(RichMessagingData.KEY_TYPE));
if (type >= EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE && type <= EventsLogApi.TYPE_OUTGOING_GROUP_CHAT_MESSAGE) {
isGroupChat = true;
}
}
if (logger.isActivated()) {
logger.debug("DeleteSession: rows to be deleted : " + toBeDeletedRows);
}
count.close();
if (toBeDeletedRows == 0) {
return;
}
if (!isGroupChat) {
// Manage recycling
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_TIMESTAMP, RichMessagingData.KEY_NUMBER_MESSAGES,
RichMessagingData.KEY_CHAT_SESSION_ID }, RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'", null,
RichMessagingData.KEY_TIMESTAMP + " DESC");
if (c.moveToFirst()) {
long maxDate = c.getLong(0);
int numberForLast = c.getInt(1);
String lastSessionId = c.getString(2);
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_NUMBER_MESSAGES, numberForLast - toBeDeletedRows);
if (sessionId.equals(lastSessionId)) {
// Find the last message from another session for the same contact
if (logger.isActivated()) {
logger.debug("DeleteSession : the deleted session is the last one... looking for the previous one for this contact");
}
while (c.moveToNext()) {
if (!sessionId.equals(c.getString(2))) {
maxDate = c.getLong(0);
if (logger.isActivated()) {
logger.debug("DeleteSession : find the previous session with date " + maxDate);
}
break;
}
}
}
if (logger.isActivated()) {
logger.debug("DeleteSession : updating the row of date " + maxDate);
}
/*
* TODO : If no more session exists after deleting this one for this contact, the update is useless because it will
* be made on the session to be deleted.
*/
int updatedRows = cr.update(databaseUri, values, RichMessagingData.KEY_TIMESTAMP + " = " + maxDate + " AND "
+ RichMessagingData.KEY_CONTACT + " = \'" + contact + "\'", null);
if (logger.isActivated()) {
logger.debug("DeleteSession : recycling updated rows (should be 1) : " + updatedRows);
}
}
c.close();
}
// Delete entry
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_CHAT_SESSION_ID + " = " + sessionId, null);
if (logger.isActivated()) {
logger.debug("DeleteSession: deleted rows : " + deletedRows);
}
}
/**
* Clear the history for a given contact
*
* @param contact Contact
*/
public void clearHistory(String contact) {
int deletedRows = cr.delete(databaseUri, RichMessagingData.KEY_CONTACT + "='" + contact + "'", null);
if (logger.isActivated()) {
logger.debug("Clear history of contact " + contact + ": deleted rows =" + deletedRows);
}
}
/**
* Delete an entry (chat message or file transfer) from its row id
*
* @param rowId Row ID
*/
public void deleteEntry(long rowId) {
Cursor count = cr.query(Uri.withAppendedPath(databaseUri, "" + rowId), null, null, null, null);
if (count.getCount() == 0) {
count.close();
return;
}
count.moveToFirst();
String contactNumber = count.getString(count.getColumnIndexOrThrow((RichMessagingData.KEY_CONTACT)));
// Manage recycling
Cursor c = cr.query(databaseUri, new String[] { RichMessagingData.KEY_TIMESTAMP, RichMessagingData.KEY_NUMBER_MESSAGES,
RichMessagingData.KEY_CHAT_SESSION_ID, RichMessagingData.KEY_DATA }, RichMessagingData.KEY_CONTACT + " = \'" + contactNumber + "\'",
null, RichMessagingData.KEY_TIMESTAMP + " DESC");
// Get the first last entry for this contact
if (c.moveToFirst()) {
long maxDate = c.getLong(0);
int numberForLast = c.getInt(1);
String lastSessionId = c.getString(2);
// We are going to delete one message
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_NUMBER_MESSAGES, numberForLast - 1);
// Check if this message has the same sessionID, timestamp and content as the one to be deleted
String sessionId = count.getString(count.getColumnIndexOrThrow(RichMessagingData.KEY_CHAT_SESSION_ID));
long date = count.getLong(count.getColumnIndexOrThrow(RichMessagingData.KEY_TIMESTAMP));
String message = "" + count.getString(count.getColumnIndexOrThrow(RichMessagingData.KEY_DATA));
if (sessionId.equals(lastSessionId) && (date == maxDate) && message.equals("" + c.getString(3))) {
/*
* It's the lastest message for this contact, find the previous message for the same contact
*/
if (logger.isActivated()) {
logger.debug("DeleteSession : the deleted message is the last one... looking for the previous one for this contact");
}
// Find the date of the previous message
if (c.moveToNext()) {
maxDate = c.getLong(0);
if (logger.isActivated()) {
logger.debug("DeleteSession : find the previous message with date " + maxDate);
}
}
}
if (logger.isActivated()) {
logger.debug("DeleteSession : updating the row of date " + maxDate);
}
/*
* Update the first message or the previous one with the new number of message for this contact.
*
* TODO : If the first message is the message to be deleted and no more messages are available for the same contact,
* then the update is useless because it will be made on the message to be deleted.
*/
int updatedRows = cr.update(databaseUri, values, RichMessagingData.KEY_TIMESTAMP + " = " + maxDate + " AND "
+ RichMessagingData.KEY_CONTACT + " = \'" + contactNumber + "\'", null);
if (logger.isActivated()) {
logger.debug("DeleteSession : recycling updated rows (should be 1) : " + updatedRows);
}
}
count.close();
c.close();
// Delete entry
int deletedRows = cr.delete(Uri.withAppendedPath(databaseUri, "" + rowId), null, null);
if (logger.isActivated()) {
logger.debug("DeleteSession: deleted rows : " + deletedRows);
}
}
/**
* Check if a session is terminated
*
* @param sessionId Session ID
* @return Boolean
*/
public boolean isSessionTerminated(String sessionId) {
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_STATUS }, RichMessagingData.KEY_CHAT_SESSION_ID + " = "
+ sessionId, null, RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
int status = cursor.getInt(0);
if ((status == EventsLogApi.STATUS_TERMINATED) || (status == EventsLogApi.STATUS_TERMINATED_BY_REMOTE)
|| (status == EventsLogApi.STATUS_TERMINATED_BY_USER)) {
cursor.close();
return true;
}
}
cursor.close();
return false;
}
/**
* Check if it's a new message
*
* @param chatId chat ID
* @param msgId message ID
* @return true if new message
*/
public boolean isNewMessage(String chatId, String msgId) {
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_MESSAGE_ID }, "(" + RichMessagingData.KEY_CHAT_ID + " = '"
+ chatId + "') AND (" + RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId + "\")", null, null);
if (cursor.moveToFirst()) {
cursor.close();
return false;
}
cursor.close();
return true;
}
/**
* doesMessageIdAlreadyExist
*
* @param msgId message ID
* @return true if the message ID already exists
*/
public boolean doesMessageIdAlreadyExist(String msgId) {
Cursor cursor = null;
try {
cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_MESSAGE_ID }, "(" + RichMessagingData.KEY_MESSAGE_ID + " = \""
+ msgId + "\")", null, null);
int messagesNumber = cursor.getCount();
return messagesNumber != 0;
} catch (Exception e) {
return false;
} finally {
if (cursor != null)
cursor.close();
}
}
/**
* Get all outgoing messages still marked undisplayed for a given contact
*
* @param contact Contact
* @return list of ids of the undisplayed messages
*/
public List<String> getAllOutgoingUndisplayedMessages(String contact) {
List<String> msgIds = new ArrayList<String>();
Cursor cursor = cr.query(
databaseUri,
new String[] { RichMessagingData.KEY_MESSAGE_ID },
RichMessagingData.KEY_CONTACT + "=?" + " AND " + RichMessagingData.KEY_TYPE + "=?" + " AND (" + RichMessagingData.KEY_STATUS + "=?"
+ " OR " + RichMessagingData.KEY_STATUS + "=?)",
new String[] { contact, String.valueOf(EventsLogApi.TYPE_OUTGOING_CHAT_MESSAGE), String.valueOf(EventsLogApi.STATUS_SENT),
String.valueOf(EventsLogApi.STATUS_DELIVERED) }, null);
while (cursor.moveToNext()) {
msgIds.add(cursor.getString(0));
}
cursor.close();
return msgIds;
}
/**
* Get the group chat ID from its session ID
*
* @param sessionId Session ID
* @result Chat ID or null
*/
public String getGroupChatId(String sessionId) {
String result = null;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_CHAT_ID }, "(" + RichMessagingData.KEY_CHAT_SESSION_ID + "='"
+ sessionId + "') AND (" + RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + ") AND ("
+ RichMessagingData.KEY_CHAT_ID + " NOT NULL)", null, RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
result = cursor.getString(0);
}
cursor.close();
return result;
}
/**
* Get the group chat rejoin ID
*
* @param chatId Chat ID
* @result Rejoin ID or null
*/
public String getGroupChatRejoinId(String chatId) {
String result = null;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_CHAT_REJOIN_ID }, "(" + RichMessagingData.KEY_CHAT_ID + "='"
+ chatId + "') AND (" + RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + ") AND ("
+ RichMessagingData.KEY_CHAT_REJOIN_ID + " NOT NULL)", null, RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
result = cursor.getString(0);
}
cursor.close();
return result;
}
/**
* Get the group chat info
*
* @param chatId Chat ID
* @result Group chat info
*/
public GroupChatInfo getGroupChatInfo(String chatId) {
GroupChatInfo result = null;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_CHAT_SESSION_ID, RichMessagingData.KEY_CHAT_REJOIN_ID,
RichMessagingData.KEY_CONTACT, RichMessagingData.KEY_DATA }, "(" + RichMessagingData.KEY_CHAT_ID + "='" + chatId + "') AND ("
+ RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + ") AND ((" + RichMessagingData.KEY_STATUS + "="
+ EventsLogApi.EVENT_INITIATED + ") OR (" + RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_INVITED + "))", null,
RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
String participants = cursor.getString(2);
List<String> list = Arrays.asList(RichMessaging.getParticipants(participants));
result = new GroupChatInfo(cursor.getString(0), cursor.getString(1), chatId, list, cursor.getString(3));
}
cursor.close();
return result;
}
/**
* Get chat Session ID associated to message ID
*
* @param msgId message Id
* @return the session ID or null if message ID does not exist
*/
public String getChatSessionIdForMessageId(String msgId) {
Cursor cursor = null;
try {
cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_CHAT_SESSION_ID }, "(" + RichMessagingData.KEY_MESSAGE_ID + " = \"" + msgId
+ "\")", null, null);
if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} catch (Exception e) {
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
/**
* Get group chat status
*
* @param chatId Chat ID
* @return Status
*/
public int getGroupChatStatus(String chatId) {
int result = -1;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_STATUS }, "(" + RichMessagingData.KEY_CHAT_ID + "='" + chatId
+ "') AND (" + RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + ")", null,
RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
result = cursor.getInt(0);
}
cursor.close();
return result;
}
/**
* Get the group chat participants who have been connected to the chat
*
* @param chatId Chat ID
* @result List of contacts
*/
public List<String> getGroupChatConnectedParticipants(String chatId) {
List<String> result = new ArrayList<String>();
List<String> alreadyTreated = new ArrayList<String>();
Cursor cursor = cr.query(databaseUri,
// We take the statuses for each contact, in descending order
new String[] { RichMessagingData.KEY_CONTACT, RichMessagingData.KEY_STATUS }, RichMessagingData.KEY_CHAT_ID + "= ?" + " AND "
+ RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + " AND (" + RichMessagingData.KEY_STATUS
+ "=" + EventsLogApi.EVENT_BUSY + " OR " + RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_DISCONNECT_CHAT + " OR "
+ RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_INITIATED + " OR " + RichMessagingData.KEY_STATUS + "="
+ EventsLogApi.EVENT_INVITED + " OR " + RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_JOINED_CHAT + " OR "
+ RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_PENDING + " OR " + RichMessagingData.KEY_STATUS + "="
+ EventsLogApi.EVENT_LEFT_CHAT + " OR " + RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_FAILED + " OR "
+ RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_DECLINED + " OR " + RichMessagingData.KEY_STATUS + "="
+ EventsLogApi.STATUS_TERMINATED_BY_USER + ")", new String[] { chatId }, RichMessagingData.KEY_TIMESTAMP + " DESC");
while (cursor.moveToNext()) {
String contact = cursor.getString(0);
int status = cursor.getInt(1);
for (String participant : RichMessaging.getParticipants(contact)) {
// Only consider history until user left voluntary the group chat
if (status == EventsLogApi.STATUS_TERMINATED_BY_USER) {
cursor.close();
return result;
}
// If we already have a status for this contact, do not take it (we only want the last one)
if (!alreadyTreated.contains(participant) && status != EventsLogApi.EVENT_LEFT_CHAT && status != EventsLogApi.EVENT_FAILED
&& status != EventsLogApi.EVENT_DECLINED && status != EventsLogApi.EVENT_BUSY) {
result.add(participant);
}
alreadyTreated.add(participant);
}
}
cursor.close();
return result;
}
/**
* Get the group chat subject
*
* @param chatId Chat ID
* @result Subject or null
*/
public String getGroupChatSubject(String chatId) {
String result = null;
Cursor cursor = cr.query(databaseUri, new String[] { RichMessagingData.KEY_DATA }, "(" + RichMessagingData.KEY_CHAT_ID + "='" + chatId
+ "') AND (" + RichMessagingData.KEY_TYPE + "=" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE + ") AND ("
+ RichMessagingData.KEY_STATUS + "=" + EventsLogApi.EVENT_INITIATED + " OR " + RichMessagingData.KEY_STATUS + "="
+ EventsLogApi.EVENT_INVITED + ") AND (" + RichMessagingData.KEY_DATA + "!='')", null, RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToFirst()) {
result = cursor.getString(0);
}
cursor.close();
return result;
}
/**
* Quit idle group chat
*
* @param sessionId
* @param chatId
*/
public void quitIdleGroupChat(String sessionId, String chatId) {
List<String> participants = getGroupChatConnectedParticipants(chatId);
addEntry(EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE, sessionId, chatId, null, participants, null, null, null, null, 0L,
null, EventsLogApi.STATUS_TERMINATED_BY_USER, true);
if (logger.isActivated()) {
logger.debug("quitIdleGroup (sessionID=" + sessionId + ") (chatID=" + chatId + ") (contact="
+ getParticipants(participants) + ")");
}
}
/**
* @param chatId
*/
public void acceptGroupChatNextInvitation(String chatId) {
ContentValues values = new ContentValues();
values.put(RichMessagingData.KEY_REJECT_GC, "0");
String selection = RichMessagingData.KEY_CHAT_ID + " = ? AND " //
+ RichMessagingData.KEY_TYPE + " = ? AND "//
+ RichMessagingData.KEY_STATUS + " = ? AND "//
+ RichMessagingData.KEY_REJECT_GC + " = 1";
String[] selectionArgs = { chatId, "" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE,
"" + EventsLogApi.STATUS_TERMINATED_BY_USER };
cr.update(databaseUri, values, selection, selectionArgs);
if (logger.isActivated()) {
logger.debug("acceptGroupChatNextInvitation (chatID=" + chatId+")");
}
}
/**
* Is group chat next Invite rejected
*
* @param chatId Chat ID
* @return true if next GC should be rejected
*/
public boolean isGroupChatNextInviteRejected(String chatId) {
boolean result = false;
String selection = RichMessagingData.KEY_CHAT_ID + " = ? AND " //
+ RichMessagingData.KEY_TYPE + " = ? AND "//
+ RichMessagingData.KEY_STATUS + " = ? AND "//
+ RichMessagingData.KEY_REJECT_GC + " = 1";
String[] selectionArgs = { chatId, "" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE,
"" + EventsLogApi.STATUS_TERMINATED_BY_USER };
Cursor cursor = cr.query(databaseUri, null, selection, selectionArgs, RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.getCount() != 0) {
result = true;
}
cursor.close();
return result;
}
/**
* Has the last known state changed for a participant
*
* @param chatId
* @param participant
* @param lastState
* @return true if the state has changed for the participant since the last time
*/
public boolean hasLastKnownStateForParticipantChanged(String chatId, String participant, String lastState){
int lastKnownState = -1;
String selection = RichMessagingData.KEY_CHAT_ID + " = ? AND " //
+ RichMessagingData.KEY_TYPE + " = ? AND "//
+ RichMessagingData.KEY_CONTACT + " = ? ";
String[] selectionArgs = { chatId,
"" + EventsLogApi.TYPE_GROUP_CHAT_SYSTEM_MESSAGE,
participant };
Cursor cursor = cr.query(databaseUri,
new String[]{RichMessagingData.KEY_STATUS},
selection,
selectionArgs,
RichMessagingData.KEY_TIMESTAMP + " DESC");
if (cursor.moveToNext()){
lastKnownState = cursor.getInt(0);
}
cursor.close();
return (lastKnownState==-1 // There was no known state yet
|| getEventLogValue(lastState)!=lastKnownState); // Or the state has changed
}
}