/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at
* src/com/vodafone360/people/VODAFONE.LICENSE.txt or
* http://github.com/360/360-Engine-for-Android
* See the License for the specific language governing permissions and
* limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each file and
* include the License file at src/com/vodafone360/people/VODAFONE.LICENSE.txt.
* If applicable, add the following below this CDDL HEADER, with the fields
* enclosed by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
* Copyright 2010 Vodafone Sales & Services Ltd. All rights reserved.
* Use is subject to license terms.
*/
package com.vodafone360.people.engine.presence;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import android.os.Bundle;
import com.vodafone360.people.database.DatabaseHelper;
import com.vodafone360.people.database.tables.MePresenceCacheTable;
import com.vodafone360.people.database.tables.ActivitiesTable.TimelineSummaryItem;
import com.vodafone360.people.datatypes.BaseDataType;
import com.vodafone360.people.datatypes.ChatMessage;
import com.vodafone360.people.datatypes.Conversation;
import com.vodafone360.people.datatypes.Identity;
import com.vodafone360.people.datatypes.PresenceList;
import com.vodafone360.people.datatypes.PushAvailabilityEvent;
import com.vodafone360.people.datatypes.PushChatMessageEvent;
import com.vodafone360.people.datatypes.PushClosedConversationEvent;
import com.vodafone360.people.datatypes.PushEvent;
import com.vodafone360.people.datatypes.ServerError;
import com.vodafone360.people.datatypes.SystemNotification;
import com.vodafone360.people.datatypes.ContactSummary.OnlineStatus;
import com.vodafone360.people.datatypes.SystemNotification.Tags;
import com.vodafone360.people.engine.BaseEngine;
import com.vodafone360.people.engine.EngineManager;
import com.vodafone360.people.engine.IEngineEventCallback;
import com.vodafone360.people.engine.EngineManager.EngineId;
import com.vodafone360.people.engine.login.LoginEngine.ILoginEventsListener;
import com.vodafone360.people.engine.meprofile.SyncMeDbUtils;
import com.vodafone360.people.engine.presence.NetworkPresence.SocialNetwork;
import com.vodafone360.people.service.ServiceStatus;
import com.vodafone360.people.service.ServiceUiRequest;
import com.vodafone360.people.service.agent.UiAgent;
import com.vodafone360.people.service.io.ResponseQueue.DecodedResponse;
import com.vodafone360.people.service.io.api.Chat;
import com.vodafone360.people.service.io.api.Presence;
import com.vodafone360.people.service.transport.ConnectionManager;
import com.vodafone360.people.service.transport.tcp.ITcpConnectionListener;
import com.vodafone360.people.utils.LogUtils;
/**
* Handles the Presence life cycle
*/
public class PresenceEngine extends BaseEngine implements ILoginEventsListener,
ITcpConnectionListener {
/** Check every 24 hours **/
private final static long CHECK_FREQUENCY = 24 * 60 * 60 * 1000;
private DatabaseHelper mDbHelper;
private final Hashtable<String, ChatMessage> mSendMessagesHash; // (to, message)
/**
* List of not delivered messages.
*/
private final List<TimelineSummaryItem> mFailedMessagesList;
/** The list of Users still to be processed. **/
private List<User> mUsers = null;
/**
* This state indicates there are no more pending presence payload
* information to be processed.
**/
private static final int IDLE = 0;
/**
* This state indicates there are some pending presence payload information
* to be processed.
**/
private static final int UPDATE_PROCESSING_GOING_ON = 1;
/** Timeout between each presence update processing. **/
private static final long UPDATE_PRESENCE_TIMEOUT_MILLS = 0;
/** The page size i.e the number of presence updates processed at a time. **/
private static final int UPDATE_PRESENCE_PAGE_SIZE = 10;
/** The number of pages after which the HandlerAgent is notified. **/
private static final int NOTIFY_AGENT_PAGE_INTERVAL = 5;
/** The state of the presence Engine. **/
private int mState = IDLE;
/**
* Number of pages of presence Updates done. This is used to control when a
* notification is sent to the UI.
**/
private int mIterations = 0;
/**
* True if the engine runs for the 1st time.
*/
private boolean mFirstRun = true;
/**
* The Hashtable of setAvailability requests that contains the pairs (networkName, targetState)
*/
private final Hashtable<String, Bundle> mSetAvailabilityUiCalls;
/**
* The request id key.
*/
private static final String REQUEST_ID_KEY = "reqId";
/**
*
* @param eventCallback
* @param databaseHelper
*/
public PresenceEngine(IEngineEventCallback eventCallback, DatabaseHelper databaseHelper) {
super(eventCallback);
mEngineId = EngineId.PRESENCE_ENGINE;
mDbHelper = databaseHelper;
mSendMessagesHash = new Hashtable<String, ChatMessage>();
mFailedMessagesList = new ArrayList<TimelineSummaryItem>();
mSetAvailabilityUiCalls = new Hashtable<String, Bundle>();
}
@Override
public void onCreate() {
}
@Override
public void onDestroy() {
if (mDbHelper != null && SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper) != null) {
PresenceDbUtils.resetPresenceStatesAcceptForMe(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper),
mDbHelper);
}
EngineManager.getInstance().getLoginEngine().removeListener(this);
}
/**
* Checks if the SyncMe and ContactSync Engines have both completed first time sync.
*
* @return true if both engines have completed first time sync
*/
private boolean isFirstTimeSyncComplete() {
return EngineManager.getInstance().getSyncMeEngine().isFirstTimeMeSyncComplete() &&
EngineManager.getInstance().getContactSyncEngine().isFirstTimeSyncComplete();
}
@Override
public long getNextRunTime() {
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED || !isLoggedIn()) {
return -1;
}
if (!isFirstTimeSyncComplete()) {
LogUtils.logV("PresenceEngine.getNextRunTime(): 1st contact sync is not finished:");
return -1;
}
/**
* need isUiRequestOutstanding() because currently the worker thread is
* running and finishing before PresenceEngine.setNextRuntime is called
*/
if (isCommsResponseOutstanding() || isUiRequestOutstanding()) {
LogUtils.logV("PresenceEngine getNextRunTime() comms response outstanding");
return 0;
}
if (mFirstRun) {
getPresenceList();
initSetMyAvailabilityRequestAfterLogin();
mFirstRun = false;
}
return getCurrentTimeout();
}
@Override
public void run() {
LogUtils.logV("PresenceEngine.run() isCommsResponseOutstanding["
+ isCommsResponseOutstanding() + "] mNextRuntime["
+ getCurrentTimeout() + "]");
if (isCommsResponseOutstanding() && processCommsInQueue()) {
LogUtils.logV("PresenceEngine.run() handled processCommsInQueue()");
return;
}
if (processTimeout()) {
LogUtils.logV("PresenceEngine.run() handled processTimeout()");
return;
}
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
LogUtils.logV("PresenceEngine.run(): AgentState.DISCONNECTED");
setPresenceOffline();
}
/**
* and the getNextRunTime must check the uiRequestReady() function and
* return 0 if it is true
*/
if (uiRequestReady() && processUiQueue()) {
return;
}
}
/**
* Helper function which returns true if a UI request is waiting on the
* queue and we are ready to process it.
*
* @return true if the request can be processed now.
*/
private boolean uiRequestReady() {
return isUiRequestOutstanding();
}
private User getMyAvailabilityStatusFromDatabase() {
return PresenceDbUtils.getMeProfilePresenceStatus(mDbHelper);
}
@Override
public void onLoginStateChanged(boolean loggedIn) {
LogUtils.logI("PresenceEngine.onLoginStateChanged() loggedIn[" + loggedIn + "]");
if (loggedIn) {
mFirstRun = true;
mFailedMessagesList.clear();
mSendMessagesHash.clear();
setPresenceOffline();
}
}
/***
* Set the Global presence status to offline.
*/
private synchronized void setPresenceOffline() {
PresenceDbUtils.setPresenceOfflineInDatabase(mDbHelper);
// We clear the mUsers of any pending presence updates because this
// Offline presence update request should take the highest priority.
mUsers = null;
mState = IDLE;
mEventCallback.getUiAgent().updatePresence(UiAgent.ALL_USERS);
}
@Override
protected void onRequestComplete() {
LogUtils.logI("PresenceEngine.onRequestComplete()");
}
@Override
protected void onTimeoutEvent() {
LogUtils.logI("PresenceEngine.onTimeoutEvent()");
switch (mState) {
case UPDATE_PROCESSING_GOING_ON:
updatePresenceDatabaseNextPage();
break;
case IDLE:
default:
if (isLoggedIn()) {
if (isFirstTimeSyncComplete()) {
getPresenceList();
initSetMyAvailabilityRequest(getMyAvailabilityStatusFromDatabase());
// Request to update the UI
setTimeout(CHECK_FREQUENCY);
} else { // check after 30 seconds
LogUtils.logE("Can't run PresenceEngine before the contact"
+ " list is downloaded:3 - set next runtime in 30 seconds");
setTimeout(CHECK_FREQUENCY / 20);
}
}
break;
}
}
@Override
protected void processCommsResponse(DecodedResponse resp) {
handleServerResponse(resp.mDataTypes);
}
private void showErrorNotification(ServiceUiRequest errorEvent, ChatMessage msg) {
UiAgent uiAgent = mEventCallback.getUiAgent();
if (uiAgent != null && uiAgent.isSubscribedWithChat()) {
uiAgent.sendUnsolicitedUiEvent(errorEvent, null);
}
}
/**
* Here we update the PresenceTable, and the ContactSummaryTable afterwards
* the HandlerAgent receives the notification of presence states changes.
*
* @param users List of users that require updating.
*/
private synchronized void updatePresenceDatabase(List<User> users) {
if (users == null || users.size() == 0) {
LogUtils.logW("PresenceEngine.updatePresenceDatabase() users is NULL or zero size");
} else {
if (mUsers == null) {
mUsers = users;
} else {
// Doing an add one by one is not an efficient way but then
// there is an issue in the addAll API. It crashes sometimes.
// And the java code for the AddAll API seems to be erroneous.
// More investigation is needed on this.
for (User user : users) {
mUsers.add(user);
}
}
}
mState = UPDATE_PROCESSING_GOING_ON;
updatePresenceDatabaseNextPage();
}
/**
* This API makes the presence updates in pages of 10 with a timeout
* after each page. The HandlerAgent is notified after every 10 pages.
*/
private synchronized void updatePresenceDatabaseNextPage() {
UiAgent uiAgent = mEventCallback.getUiAgent();
if(mUsers == null){
mState = IDLE;
return;
}
int listSize = mUsers.size();
int start = 0;
int end = UPDATE_PRESENCE_PAGE_SIZE;
if (listSize == 0) {
mState = IDLE;
mUsers = null;
notifyUiAgentOfResponse();
return;
} else if(listSize < end) {
end = listSize;
}
List<User> userSubset = mUsers.subList(start, end);
if ((userSubset != null)) {
long idListeningTo = UiAgent.ALL_USERS;
if (uiAgent != null) {
idListeningTo = uiAgent.getLocalContactId();
}
boolean updateUI = PresenceDbUtils.updateDatabase(userSubset, idListeningTo, mDbHelper);
userSubset.clear();
// Send the update notification to UI for every UPDATE_PRESENCE_PAGE_SIZE*NOTIFY_AGENT_PAGE_INTERVAL updates.
if (updateUI) {
if (mUsers.size() == 0 || NOTIFY_AGENT_PAGE_INTERVAL == mIterations) {
if (uiAgent != null) {
uiAgent.updatePresence(idListeningTo);
}
mIterations = 0;
} else {
mIterations++;
}
}
this.setTimeout(UPDATE_PRESENCE_TIMEOUT_MILLS);
}
}
/**
* Updates the database with the given ChatMessage and Type.
*
* @param message ChatMessage.
* @param type TimelineSummaryItem.Type.
*/
private void updateChatDatabase(ChatMessage message, TimelineSummaryItem.Type type) {
ChatDbUtils.convertUserIds(message, mDbHelper);
LogUtils.logD("PresenceEngine.updateChatDatabase() with [" + type.name() + "] message");
if (message.getLocalContactId() == null || message.getLocalContactId() < 0) {
LogUtils.logE("PresenceEngine.updateChatDatabase() "
+ "WILL NOT UPDATE THE DB! - INVALID localContactId = "
+ message.getLocalContactId());
return;
}
/** We mark all incoming messages as unread. **/
ChatDbUtils.saveChatMessageAsATimeline(message, type, mDbHelper);
UiAgent uiAgent = mEventCallback.getUiAgent();
if (uiAgent != null && (message.getLocalContactId() != -1)) {
uiAgent.updateChat(message.getLocalContactId(), true, message.getNetworkId());
}
}
/**
* Here we update the PresenceTable, and the ContactSummaryTable afterwards
* the HandlerAgent receives the notification of presence states changes.
*
* @param users User that requires updating.
*/
private void updateMyPresenceInDatabase(User myself) {
LogUtils.logV("PresenceEnfgine.updateMyPresenceInDatabase() myself[" + myself + "]");
if (PresenceDbUtils.updateMyPresence(myself, mDbHelper)) {
mEventCallback.getUiAgent().updatePresence(myself.getLocalContactId());
}
}
/**
* This method handles incoming presence status change push events and the
* whole PresenceList
*
* @param dataTypes
*/
private void handleServerResponse(List<BaseDataType> dataTypes) {
if (dataTypes != null) {
for (BaseDataType mBaseDataType : dataTypes) {
int type = mBaseDataType.getType();
if (type == BaseDataType.PRESENCE_LIST_DATA_TYPE) {
handlePresenceList((PresenceList)mBaseDataType);
} else if (type == BaseDataType.PUSH_EVENT_DATA_TYPE) {
handlePushEvent(((PushEvent)mBaseDataType));
} else if (type == BaseDataType.CONVERSATION_DATA_TYPE) {
// a new conversation has just started
handleNewConversationId((Conversation)mBaseDataType);
} else if (type == BaseDataType.SYSTEM_NOTIFICATION_DATA_TYPE) {
handleSystemNotification((SystemNotification)mBaseDataType);
} else if (type == BaseDataType.SERVER_ERROR_DATA_TYPE) {
handleServerError((ServerError)mBaseDataType);
} else {
LogUtils.logE("PresenceEngine.handleServerResponse()"
+ ": response datatype not recognized:" + type);
}
}
} else {
LogUtils.logE("PresenceEngine.handleServerResponse(): response is null!");
}
}
private void handlePresenceList(PresenceList presenceList) {
updatePresenceDatabase(presenceList.getUsers());
}
private void handleServerError(ServerError srvError) {
LogUtils.logE("PresenceEngine.handleServerResponse() - Server error: " + srvError);
ServiceStatus errorStatus = srvError.toServiceStatus();
if (errorStatus == ServiceStatus.ERROR_COMMS_TIMEOUT) {
LogUtils.logW("PresenceEngine handleServerResponce(): TIME OUT IS RETURNED TO PRESENCE ENGINE.");
notifyUiAgentOfTimeOut(srvError.requestId);
}
}
private void handleNewConversationId(Conversation conversation) {
if (conversation.getTos() != null) {
String to = conversation.getTos().get(0);
if (mSendMessagesHash.containsKey(to)) {
ChatMessage message = mSendMessagesHash.get(to);
message.setConversationId(conversation.getConversationId());
/** Update the DB with an outgoing message. **/
updateChatDatabase(message, TimelineSummaryItem.Type.OUTGOING);
Chat.sendChatMessage(message);
// clean check if DB needs a cleaning (except for
// the current conversation id)
ChatDbUtils.cleanOldConversationsExceptForContact(message.getLocalContactId(),
mDbHelper);
}
}
}
private void handlePushEvent(PushEvent event) {
switch (event.mMessageType) {
case AVAILABILITY_STATE_CHANGE:
PushAvailabilityEvent pa = (PushAvailabilityEvent)event;
updatePresenceDatabase(pa.mChanges);
break;
case CHAT_MESSAGE:
PushChatMessageEvent pc = (PushChatMessageEvent)event;
// update the DB with an incoming message
updateChatDatabase(pc.getChatMsg(), TimelineSummaryItem.Type.INCOMING);
break;
case CLOSED_CONVERSATION:
PushClosedConversationEvent pcc = (PushClosedConversationEvent)event;
// delete the conversation in DB
if (pcc.getConversation() != null) {
ChatDbUtils.deleteConversationById(pcc.getConversation(), mDbHelper);
}
break;
default:
LogUtils.logE("PresenceEngine.handleServerResponse():"
+ " push message type was not recognized:" + event.getType());
}
}
private void handleSystemNotification(SystemNotification sn) {
LogUtils.logE("PresenceEngine.handleServerResponse(): " + sn);
switch (sn.getSysCode()) {
case SEND_MESSAGE_FAILED:
ChatMessage msg = mSendMessagesHash.get(sn.getInfo().get(Tags.TOS.toString()));
if (msg != null) {
ChatDbUtils.deleteUnsentMessage(mDbHelper, msg);
}
showErrorNotification(ServiceUiRequest.UNSOLICITED_CHAT_ERROR_REFRESH, null);
break;
case CONVERSATION_NULL:
if (!mSendMessagesHash.isEmpty()) {
mSendMessagesHash.remove(sn.getInfo().get(Tags.TOS.toString()));
}
showErrorNotification(ServiceUiRequest.UNSOLICITED_CHAT_ERROR, null);
break;
case COMMUNITY_LOGOUT_SUCCESSFUL:
break;
default:
LogUtils.logE("PresenceEngine.handleServerResponse()"
+ " - unhandled notification: " + sn);
}
}
@Override
protected void processUiRequest(ServiceUiRequest requestId, Object data) {
if (!isFirstTimeSyncComplete()) {
LogUtils.logE("PresenceEngine.processUIRequest():"
+ " Can't run PresenceEngine before the contact list is downloaded:1");
return;
}
LogUtils.logW("PresenceEngine.processUiRequest() requestId.name[" + requestId.name() + "]");
if(data == null && requestId != ServiceUiRequest.GET_PRESENCE_LIST) {
LogUtils.logW("PresenceEngine.processUiRequest() skipping processing for request with no data!");
return;
}
switch (requestId) {
case SET_MY_AVAILABILITY:
Hashtable<String, String> presenceHash = (Hashtable<String, String>)data;
notifyUiAgentOfRequest(Presence.setMyAvailability(presenceHash), presenceHash);
break;
case GET_PRESENCE_LIST:
Presence.getPresenceList(EngineId.PRESENCE_ENGINE, null);
break;
case CREATE_CONVERSATION:
List<String> tos = ((ChatMessage)data).getTos();
LogUtils.logW("PresenceEngine processUiRequest() CREATE_CONVERSATION with: "
+ tos);
Chat.startChat(tos);
break;
case SEND_CHAT_MESSAGE:
ChatMessage msg = (ChatMessage)data;
updateChatDatabase(msg, TimelineSummaryItem.Type.OUTGOING);
//cache the message (necessary for failed message sending failures)
mSendMessagesHash.put(msg.getTos().get(0), msg);
Chat.sendChatMessage(msg);
break;
default:
LogUtils.logE("PresenceEngine processUiRequest() Unhandled UI request ["
+ requestId.name() + "]");
return; // don't complete with success and schedule the next runtime
}
completeUiRequest(ServiceStatus.SUCCESS, null);
setTimeout(CHECK_FREQUENCY);
}
/**
* Initiate the "get presence list" request sending to server. Makes the
* engine run asap.
*
* @return
*/
synchronized public void getPresenceList() {
addUiRequestToQueue(ServiceUiRequest.GET_PRESENCE_LIST, null);
}
/**
* Method used to jump start setting the availability.
* This is primarily used for reacting to login/connection state changes.
* @param me Our User
*/
private void initSetMyAvailabilityRequest(User me) {
if (me == null) {
LogUtils.logE("PresenceEngine.initSetMyAvailabilityRequest():"
+ " Can't send the setAvailability request due to DB reading errors");
return;
}
if ((me.isOnline() == OnlineStatus.ONLINE.ordinal() &&
ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED)
|| !isFirstTimeSyncComplete()) {
LogUtils.logD("PresenceEngine.initSetMyAvailabilityRequest():"
+ " return NO NETWORK CONNECTION or not ready");
return;
}
Hashtable<String, String> availability = new Hashtable<String, String>();
for (NetworkPresence presence : me.getPayload()) {
availability.put(SocialNetwork.getSocialNetworkValue(presence.getNetworkId()).toString(),
OnlineStatus.getValue(presence.getOnlineStatusId()).toString());
}
// set the DB values
me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
updateMyPresenceInDatabase(me);
addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
}
/**
* This method notifies UiAgent of setAvailability call sent by the engine.
* @param reqId - the request id.
* @param availability - "Me" availability Hashtable - the parameter of the setAvailability call.
*/
private void notifyUiAgentOfRequest(int reqId, Hashtable<String, String> availability) {
String network = null;
String state = null;
synchronized (mSetAvailabilityUiCalls) {
for (Enumeration<String> en = availability.keys(); en.hasMoreElements();) {
network = en.nextElement();
state = availability.get(network);
final Bundle bundle = createBundle(network, state, User.SOURCE_REQUESTED);
bundle.putInt(REQUEST_ID_KEY, reqId);
mSetAvailabilityUiCalls.put(network, bundle);
fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY, bundle);
}
}
}
/**
* This method notifies UiAgent of the presence push/getPresenceList() response.
*/
private void notifyUiAgentOfResponse() {
final User user = getMyAvailabilityStatusFromDatabase();
if (user != null) {
String network = null;
String state = null;
String stateFromDb = null;
synchronized (mSetAvailabilityUiCalls) {
for (Iterator<String> it = mSetAvailabilityUiCalls.keySet().iterator(); it.hasNext();) {
network = it.next();
state = ((Bundle)mSetAvailabilityUiCalls.get(network)).getString(User.STATUS);
stateFromDb = user.getStatusForNetwork(SocialNetwork.getValue(network)).toString();
if (state.equals(stateFromDb) ||
(state.equals(OnlineStatus.INVISIBLE.toString()) &&
stateFromDb.equals(OnlineStatus.OFFLINE.toString()))) {
fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY,
createBundle(network, state, User.SOURCE_RECEIVED));
it.remove();
}
}
}
}
}
/**
* This method notifies UiAgent of the presence push not coming in a timely manner.
*/
private void notifyUiAgentOfTimeOut(int reqId) {
String network = null;
int requestId = 0;
synchronized (mSetAvailabilityUiCalls) {
for (Iterator<String> it = mSetAvailabilityUiCalls.keySet().iterator(); it.hasNext();) {
network = it.next();
requestId = ((Bundle)mSetAvailabilityUiCalls.get(network)).getInt(REQUEST_ID_KEY);
if (requestId == reqId) {
fireNewState(ServiceUiRequest.SET_MY_AVAILABILITY,
createBundle(network,
((Bundle)mSetAvailabilityUiCalls.get(network)).getString(User.STATUS),
User.SOURCE_TIMED_OUT));
it.remove();
}
}
}
}
/**
* This method creates a Bundle for sending in the notification about setAvailability() calls
* to UiAgent with the following data.
* @param network - String name of the network @see {@link SocialNetwork}
* @param status - String name of the status @see {@link OnlineStatus}
* @param sourceType - type of the status, @see User.SOURCE_TIMED_OUT, User.SOURCE_REQUESTED, SOURCE_RECEIVED.
* @return Bundle for sending in the notification about setAvailability() calls to UiAgent.
*/
private Bundle createBundle(String network, String status, int sourceType) {
final Bundle bundle = new Bundle();
bundle.putString(User.NETWORK, network);
bundle.putString(User.STATUS, status);
bundle.putInt(User.SOURCE, sourceType);
return bundle;
}
/**
* Method used to set availability of me profile when user logs in.
* This is primarily used for reacting to login/connection state changes.
*/
private void initSetMyAvailabilityRequestAfterLogin() {
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED
|| !isFirstTimeSyncComplete()) {
LogUtils.logD("PresenceEngine.initSetMyAvailabilityRequest():"
+ " return NO NETWORK CONNECTION or not ready");
return;
}
String meProfileUserId = Long.toString(PresenceDbUtils.getMeProfileUserId(mDbHelper));
Hashtable<String, String> availability = new Hashtable<String, String>();
ArrayList<Identity> identityList = EngineManager.getInstance().getIdentityEngine().getMyChattableIdentities();
if ((identityList.size() != 0)) {
for (int i=0; i<identityList.size(); i++) {
Identity myIdentity = identityList.get(i);
availability.put(myIdentity.mNetwork, OnlineStatus.ONLINE.toString());
}
}
User meUser = new User(meProfileUserId, availability);
meUser.setLocalContactId(Long.valueOf(meProfileUserId));
updateMyPresenceInDatabase(meUser);
addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
}
/**
* Changes the user's availability and therefore the state of the engine.
* Also displays the login notification if necessary.
*
* @param status Availability to set for all identities we have.
*/
public void setMyAvailability(OnlineStatus status) {
if (status == null) {
LogUtils.logE("PresenceEngine setMyAvailability:"
+ " Can't send the setAvailability request due to DB reading errors");
return;
}
LogUtils.logV("PresenceEngine setMyAvailability() called with status:"+status.toString());
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
LogUtils.logD("PresenceEnfgine.setMyAvailability(): skip - NO NETWORK CONNECTION");
return;
}
if (!isFirstTimeSyncComplete()) {
LogUtils.logD("PresenceEngine.setMyAvailability(): skip - First time contact sync has not finished yet");
return;
}
// Get presences
Hashtable<String, String> availability = getPresencesForStatus(status);
User me = new User(String.valueOf(PresenceDbUtils.getMeProfileUserId(mDbHelper)),
availability);
MePresenceCacheTable.updateCache(me.getPayload(), mDbHelper.getWritableDatabase());
// set the DB values for myself
me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
updateMyPresenceInDatabase(me);
addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
}
/**
* Changes the user's availability.
*
* @param network - SocialNetwork to set presence on.
* @param status - OnlineStatus presence status to set.
*/
public void setMyAvailability(SocialNetwork network, OnlineStatus status) {
LogUtils.logV("PresenceEngine setMyAvailability() called with network presence: "+
network + "=" + status);
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
LogUtils.logD("PresenceEnfgine.setMyAvailability(): skip - NO NETWORK CONNECTION");
return;
}
ArrayList<NetworkPresence> presenceList = new ArrayList<NetworkPresence>();
if (!isFirstTimeSyncComplete()) {
LogUtils.logD("PresenceEngine.setMyAvailability(): skip - First time contact sync has not finished yet");
return;
}
String userId = String.valueOf(PresenceDbUtils.getMeProfileUserId(mDbHelper));
final NetworkPresence newPresence = new
NetworkPresence(userId, network.ordinal(), status.ordinal());
presenceList.add(newPresence);
User me = new User(userId, null);
me.setPayload(presenceList);
MePresenceCacheTable.updateCache(newPresence, mDbHelper.getWritableDatabase());
// set the DB values for myself
me.setLocalContactId(SyncMeDbUtils.getMeProfileLocalContactId(mDbHelper));
updateMyPresenceInDatabase(me);
// set the engine to run now
Hashtable<String, String> availability = new Hashtable<String, String>();
availability.put(network.toString(), status.toString());
addUiRequestToQueue(ServiceUiRequest.SET_MY_AVAILABILITY, availability);
}
private void createConversation(ChatMessage msg) {
LogUtils.logW("PresenceEngine.createConversation():" + msg);
// as we currently support sending msgs to just one person getting the 0
// element of "Tos" must be acceptable
mSendMessagesHash.put(msg.getTos().get(0), msg);
addUiRequestToQueue(ServiceUiRequest.CREATE_CONVERSATION, msg);
}
/**
* This method should be used to send a message to a contact
*
* @param tos - tlocalContactId of ContactSummary items the message is
* intended for. Current protocol version only supports a single
* recipient.
* @param body the message text
*/
public void sendMessage(long toLocalContactId, String body, int networkId) {
if (ConnectionManager.getInstance().getConnectionState() != STATE_CONNECTED) {
LogUtils.logD("PresenceEnfgine.sendMessage: skip - NO NETWORK CONNECTION");
return;
}
ChatMessage msg = new ChatMessage();
msg.setBody(body);
msg.setNetworkId(networkId);
msg.setLocalContactId(toLocalContactId);
ChatDbUtils.fillMessageByLocalContactIdAndNetworkId(msg, mDbHelper);
if (msg.getConversationId() != null) {
String fullUserId = SocialNetwork.getSocialNetworkValue(msg.getNetworkId()).toString()
+ ChatDbUtils.COLUMNS + msg.getUserId();
msg.setUserId(fullUserId);
addUiRequestToQueue(ServiceUiRequest.SEND_CHAT_MESSAGE, msg);
} else {
// if the conversation was not found that means it didn't exist,
// need start a new one
if (msg.getUserId() == null) {
ChatDbUtils.findUserIdForMessageByLocalContactIdAndNetworkId(msg, mDbHelper);
createConversation(msg);
}
}
}
@Override
public void onConnectionStateChanged(int state) {
if (isLoggedIn() && isFirstTimeSyncComplete()) {
switch (state) {
case STATE_CONNECTED:
getPresenceList();
initializeAvailabilityFromDb();
break;
case STATE_CONNECTING:
case STATE_DISCONNECTED:
setPresenceOffline();
mFailedMessagesList.clear();
mSendMessagesHash.clear();
mSetAvailabilityUiCalls.clear();
break;
}
}
}
/**
* Initializes or re-initializes availability from the Database.
* If there are cached presences then these will be used
* instead of values from the presence table.
*/
private void initializeAvailabilityFromDb() {
final User user = getMyAvailabilityStatusFromDatabase();
ArrayList<NetworkPresence> cachedPresences =
MePresenceCacheTable.getCache(mDbHelper.getReadableDatabase());
if(cachedPresences != null) {
user.setPayload(cachedPresences);
}
initSetMyAvailabilityRequest(user);
}
/**
* Convenience method.
* Constructs a Hash table object containing My identities mapped against the provided status.
* @param status Presence status to set for all identities
* @return The resulting Hash table, is null if no identities are present
*/
public Hashtable<String, String> getPresencesForStatus(OnlineStatus status) {
// Get cached identities from the presence engine
ArrayList<Identity> identities =
EngineManager.getInstance().getIdentityEngine().getMyChattableIdentities();
if (identities == null) {
// No identities, just return null
return null;
}
Hashtable<String, String> presences = new Hashtable<String, String>();
String statusString = status.toString();
for (Identity identity : identities) {
presences.put(identity.mNetwork, statusString);
}
return presences;
}
@Override
public void onReset() {
// reset the engine as if it was just created
super.onReset();
mFirstRun = true;
mIterations = 0;
mState = IDLE;
mUsers = null;
mFailedMessagesList.clear();
mSendMessagesHash.clear();
mSetAvailabilityUiCalls.clear();
}
/**
* This method returns if the LoginEngine is in the LOGGED_ON state.
* @return - TRUE if the LoginEngine is in the LOGGED_ON state.
*/
private boolean isLoggedIn(){
return EngineManager.getInstance().getLoginEngine().isLoggedIn();
}
/**
* This method returns TRUE if the presence status on the passed has not been set yet.
* @param network - network name.
* @return TRUE if the presence status on the passed has not been set yet.
*/
public boolean isSetStatusInProgress(String network) {
return mSetAvailabilityUiCalls.containsKey(network);
}
}