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