/*
* 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.service.agent;
import java.security.InvalidParameterException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import com.vodafone360.people.ApplicationCache;
import com.vodafone360.people.Intents;
import com.vodafone360.people.MainApplication;
import com.vodafone360.people.engine.upgrade.UpgradeStatus;
import com.vodafone360.people.engine.upgrade.VersionCheck;
import com.vodafone360.people.service.ServiceUiRequest;
import com.vodafone360.people.service.transport.ConnectionManager;
import com.vodafone360.people.utils.LogUtils;
import com.vodafone360.people.utils.WidgetUtils;
/**
* The UiAgent is aware when any "Live" Activities are currently on screen, and
* contains business logic for sending unsolicited messages to the UI. This is
* useful for knowing when to send chat messages, presence updates,
* notifications, error messages, etc to an on screen Activity.
*/
public class UiAgent {
/** UI Notification identifier. **/
public static final int UI_AGENT_NOTIFICATION_ID = 1;
/** Pointer to MainApplication. **/
private MainApplication mMainApplication;
/**
* Store for pending UiEvents while no People Activities are currently on
* screen.
*/
private ServiceUiRequest mUiEventQueue = null;
/**
* Store for pending UiEvent Bundles while no People Activities are
* currently on screen.
*/
private Bundle mUiBundleQueue = null;
/**
* Handler object from a subscribed UI Activity. Note: This object is
* nullified by a separate thread and is thereby declared volatile.
*/
private volatile Handler mHandler;
/**
* Local ID of the contact the Handler is tracking (-1 when tracking all).
*/
private long mLocalContactId;
/** TRUE if the subscribed UI Activity expects chat messages. **/
private boolean mShouldHandleChat;
/** Reference to Android Context. **/
private Context mContext = null;
/**
* This constant is used to indicate the UiAgent is now subscribed
* to refresh events related to all local contact ids.
*/
public static final int ALL_USERS = -1;
/**
* Constructor.
*
* @param mainApplication Pointer to MainApplication.
* @param context Reference to Android Context.
*/
public UiAgent(final MainApplication mainApplication,
final Context context) {
mMainApplication = mainApplication;
mContext = context;
mHandler = null;
mLocalContactId = ALL_USERS;
mShouldHandleChat = false;
}
/***
* Send an unsolicited UI Event to the UI. If there are no on screen
* Activities, then queue the message for later. The queue is of size one,
* so higher priority messages will simply overwrite older ones.
*
* @param uiEvent Event to send.
* @param bundle Optional Bundle to send to UI, usually set to NULL.
*/
public final void sendUnsolicitedUiEvent(final ServiceUiRequest uiEvent,
final Bundle bundle) {
if (uiEvent == null) {
throw new InvalidParameterException("UiAgent."
+ "sendUnsolicitedUiEvent() UiEvent cannot be NULL");
}
LogUtils.logW("UiAgent.sendUnsolicitedUiEvent() uiEvent["
+ uiEvent.name() + "]");
if (mHandler != null) {
/*
* Send now.
*/
try {
mHandler.sendMessage(mHandler.obtainMessage(uiEvent.ordinal(),
bundle));
LogUtils.logV("UiAgent.sendUnsolicitedUiEvent() "
+ "Sending uiEvent[" + uiEvent + "]");
} catch (NullPointerException e) {
LogUtils.logW("UiAgent.sendUnsolicitedUiEvent() Caught a race "
+ "condition where mHandler was set to NULL after "
+ "being explicitly checked");
/** Send later anyway. **/
addUiEventToQueue(uiEvent, bundle);
}
} else {
/*
* Send later.
*/
addUiEventToQueue(uiEvent, bundle);
}
}
/***
* Add the given UiEvent and Bundle pair to the send later queue.
*
* @param uiEvent ServiceUiRequest to queue.
* @param bundle Bundle to queue.
*/
private void addUiEventToQueue(final ServiceUiRequest uiEvent,
final Bundle bundle) {
if (mUiEventQueue == null
|| uiEvent.ordinal() < mUiEventQueue.ordinal()) {
LogUtils.logV("UiAgent.sendUnsolicitedUiEvent() Sending uiEvent["
+ uiEvent.name() + "] later");
mUiEventQueue = uiEvent;
mUiBundleQueue = bundle;
} else {
LogUtils.logV("UiAgent.sendUnsolicitedUiEvent() Ignoring uiEvent["
+ uiEvent.name() + "], as highter priority UiEvent["
+ mUiEventQueue.name() + "] is already pending");
}
}
/**
* Subscribes a UI Handler to receive unsolicited events.
*
* @param handler UI handler to receive unsolicited events.
* @param localContactId Provide a local contact ID to receive updates for
* the given contact only, or set this to -1 to receive updates
* for every contact, or set this to NULL not to receive contact
* updates.
* @param shouldHandleChat TRUE if the Handler expects chat messages.
*/
public final void subscribe(final Handler handler,
final Long localContactId, final boolean shouldHandleChat) {
LogUtils.logV("UiAgent.subscribe() handler[" + handler
+ "] localContactId[" + localContactId + "] chat[" + shouldHandleChat
+ "]");
if (handler == null) {
throw new NullPointerException("UiAgent.subscribe()"
+ "Handler cannot be NULL");
}
mHandler = handler;
mLocalContactId = localContactId;
mShouldHandleChat = shouldHandleChat;
if (mUiEventQueue != null) {
LogUtils.logV("UiAgent.subscribe() Send pending uiEvent["
+ mUiEventQueue + "]");
mHandler.sendMessage(mHandler.obtainMessage(
mUiEventQueue.ordinal(), mUiBundleQueue));
mUiEventQueue = null;
mUiBundleQueue = null;
}
UpgradeStatus upgradeStatus = new VersionCheck(
mMainApplication.getApplicationContext(),
false).getCachedUpdateStatus();
if (upgradeStatus != null) {
sendUnsolicitedUiEvent(ServiceUiRequest.UNSOLICITED_DIALOG_UPGRADE,
null);
}
ConnectionManager.getInstance().notifyOfUiActivity();
}
/**
* This method ends the UI Handler's subscription. This will have no effect
* if a different handler is currently subscribed.
*
* @param handler - UI handler to no longer receive unsolicited events.
*/
public final void unsubscribe(final Handler handler) {
if (handler == null) {
throw new NullPointerException("UiAgent.unsubscribe() Handler"
+ "cannot be NULL.");
}
if (handler != mHandler) {
LogUtils.logW("UiAgent.unsubscribe() Activity is trying to "
+ "unsubscribe with a different handler");
} else {
mHandler = null;
mLocalContactId = ALL_USERS;
mShouldHandleChat = false;
}
}
/**
* This method returns the local ID of the contact the HandlerAgent is
* tracking.
*
* @return LocalContactId of the contact the HandlerAgent is tracking
*/
public final long getLocalContactId() {
return mLocalContactId;
}
/**
* Returns TRUE if an Activity is currently listening out for unsolicited
* events (i.e. a "Live" activity is currently on screen).
*
* @return TRUE if any subscriber is listening the presence state/chat
* events
*/
public final boolean isSubscribed() {
return mHandler != null;
}
/**
* Returns TRUE if an Activity is currently listening out for unsolicited
* events (i.e. a "Live" activity is currently on screen).
*
* @return TRUE if any subscriber is listening the presence chat events
*/
public final boolean isSubscribedWithChat() {
return mHandler != null && mShouldHandleChat;
}
/**
* This method is called by the Presence engine to notify a subscribed
* Activity of updates.
*
* @param contactId Update an Activity that shows this contact ID only, or
* set this to ALL_USERS to send updates relevant to all contacts.
*/
public final void updatePresence(final long contactId) {
WidgetUtils.kickWidgetUpdateNow(mMainApplication);
if (mHandler == null) {
LogUtils.logW("UiAgent.updatePresence() No subscribed Activities");
return;
}
if(shouldSendPresenceToUi(contactId)) {
mHandler.sendMessage(mHandler.obtainMessage(
ServiceUiRequest.UNSOLICITED_PRESENCE.ordinal(),
null));
} else {
LogUtils.logV("UiAgent.updatePresence() No Activities are "
+ "interested in contactId[" + contactId + "]");
}
}
/**
* Checks if a (Unsolicited) Presence event should be sent to the UI.
* At the moment sending it only if contactId or mLocalContactId for ALL_USERS
* or the monitored contact matches mLocalContactId.
* @param contactId
* @return
*/
private boolean shouldSendPresenceToUi(final long contactId) {
if(mLocalContactId == ALL_USERS) {
return true;
}
/*
* Basically this fixes bug in the me profile where presence is not updated to invisible
* when user goes into flight mode
*/
if(contactId == ALL_USERS) {
return true;
}
return mLocalContactId == contactId;
}
/**
* <p>Notifies an on screen Chat capable Activity of a relevant update. If the
* wrong Activity is on screen, this update will be shown as a Notification.</p>
*
* <p><b>Please note that this method takes care of notification for adding AND
* removing chat message notifications</b></p>!
*
* @param contactId Update an Activity that shows Chat information for this
* localContact ID only.
* @param isNewChatMessage True if this is a new chat message that we are notifying
* for. False if we are for instance just deleting a notification.
* @param networkId the ordinal value of the SocialNetwork from which the chat
* message has been received
*/
public final void updateChat(final long contactId, final boolean isNewChatMessage, final int networkId) {
if (mHandler != null && mShouldHandleChat && mLocalContactId == contactId) {
LogUtils.logV("UiAgent.updateChat() Send message to UI (i.e. "
+ "update the screen)");
mHandler.sendMessage(mHandler.obtainMessage(
ServiceUiRequest.UNSOLICITED_CHAT.ordinal(), networkId, 0, null));
/*
* Note: Do not update the chat notification at this point, as the
* UI must update the database (e.g. mark messages as read) before
* calling subscribe() to trigger a refresh.
*/
} else if (mHandler != null && mShouldHandleChat && mLocalContactId == -1) {
LogUtils.logV("UiAgent.updateChat() Send message to UI (i.e. "
+ "update the screen) and do a noisy notification");
/*
* Note: this was added because TimelineListActivity listens to all
* localIds (i.e. localContactId = -1)
*/
mHandler.sendMessage(mHandler.obtainMessage(
ServiceUiRequest.UNSOLICITED_CHAT.ordinal(), networkId, 0, null));
updateChatNotification(isNewChatMessage);
} else {
LogUtils.logV("UiAgent.updateChat() Do a noisy notification only");
updateChatNotification(isNewChatMessage);
}
}
/***
* Update the Notification bar with Chat information directly from the
* database.
*
* @param isNewChatMessage True if we have a new chat message and want to
* display a noise and notification, false if we for instance just delete a
* chat message or update a multiple-user notification silently.
*
*/
private void updateChatNotification(final boolean isNewChatMessage) {
mContext.sendBroadcast(new Intent(Intents.UPDATE_CHAT_NOTIFICATION).putExtra(
ApplicationCache.sIsNewMessage, isNewChatMessage));
}
}