/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.protocol.jabber;
import net.java.sip.communicator.impl.protocol.jabber.extensions.caps.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import org.jivesoftware.smack.util.*;
import java.util.*;
/**
* Handles all the logic about mobile indicator for contacts.
* Has to modes, the first is searching for particular string in the beginning
* of the contact resource and if found and this is the highest priority then
* the contact in on mobile.
* The second one and the default one is searching for strings in the node
* from the contact caps and if found and this is the most connected device
* then the contact is a mobile one.
*
* @author Damian Minkov
*/
public class MobileIndicator
implements RegistrationStateChangeListener,
UserCapsNodeListener
{
/**
* The parent provider.
*/
private final ProtocolProviderServiceJabberImpl parentProvider;
/**
* Whether we are using the default method for checking for
* mobile indicator.
*/
private boolean isCapsMobileIndicator = true;
/**
* The strings that we will check.
*/
private final String[] checkStrings;
/**
* A reference to the ServerStoredContactListImpl instance.
*/
private final ServerStoredContactListJabberImpl ssclCallback;
/**
* The account property to activate the mode for checking the resource
* names, the strings to check whether a resource starts with can be
* entered separated by comas.
*/
private static final String MOBILE_INDICATOR_RESOURCE_ACC_PROP =
"MOBILE_INDICATOR_RESOURCE";
/**
* The account property to activate the mode for checking the contact
* caps, the strings to check whether a caps contains with can be
* entered separated by comas.
*/
private static final String MOBILE_INDICATOR_CAPS_ACC_PROP =
"MOBILE_INDICATOR_CAPS";
/**
* Construct Mobile indicator.
* @param parentProvider the parent provider.
* @param ssclCallback the callback for the contact list to obtain contacts.
*/
public MobileIndicator(ProtocolProviderServiceJabberImpl parentProvider,
ServerStoredContactListJabberImpl ssclCallback)
{
this.parentProvider = parentProvider;
this.ssclCallback = ssclCallback;
String indicatorResource = parentProvider.getAccountID()
.getAccountProperties().get(MOBILE_INDICATOR_RESOURCE_ACC_PROP);
if(indicatorResource != null
&& indicatorResource.length() > 0)
{
isCapsMobileIndicator = false;
checkStrings = indicatorResource.split(",");
}
else
{
String indicatorCaps = parentProvider.getAccountID()
.getAccountProperties().get(MOBILE_INDICATOR_CAPS_ACC_PROP);
if(indicatorCaps == null
|| indicatorCaps.length() == 0)
{
indicatorCaps = "android";
}
checkStrings = indicatorCaps.split(",");
this.parentProvider.addRegistrationStateChangeListener(this);
}
}
/**
* Called when resources have been updated for a contact, on
* presence changed.
* @param contact the contact
*/
public void resourcesUpdated(ContactJabberImpl contact)
{
if(isCapsMobileIndicator)
{
// we update it also here, cause sometimes caps update comes
// before presence changed and contacts are still offline
// and we dispatch wrong initial mobile indicator
updateMobileIndicatorUsingCaps(contact.getAddress());
return;
}
// checks resource starts with String and is current highest priority
int highestPriority = Integer.MIN_VALUE;
List<ContactResource> highestPriorityResources =
new ArrayList<ContactResource>();
Collection<ContactResource> resources = contact.getResources();
// sometimes volatile contacts do not have resources
if(resources == null)
return;
for(ContactResource res : resources)
{
if(!res.getPresenceStatus().isOnline())
continue;
int prio = res.getPriority();
if(prio >= highestPriority)
{
if(highestPriority != prio)
highestPriorityResources.clear();
highestPriority = prio;
highestPriorityResources.add(res);
}
}
// check whether all are mobile
boolean allMobile = false;
for(ContactResource res : highestPriorityResources)
{
if(res.isMobile())
allMobile = true;
else
{
allMobile = false;
break;
}
}
if(highestPriorityResources.size() > 0)
contact.setMobile(allMobile);
else
contact.setMobile(false);
}
/**
* Checks a resource whether it is mobile or not, by checking the
* cache.
* @param resourceName the resource name to check.
* @param fullJid the jid to check.
* @return whether resource with that name is mobile or not.
*/
boolean isMobileResource(String resourceName, String fullJid)
{
if(isCapsMobileIndicator)
{
EntityCapsManager capsManager = ssclCallback.getParentProvider()
.getDiscoveryManager().getCapsManager();
EntityCapsManager.Caps caps = capsManager.getCapsByUser(fullJid);
if(caps != null && containsStrings(caps.node, checkStrings))
return true;
else
return false;
}
if(startsWithStrings(resourceName, checkStrings))
return true;
else
return false;
}
/**
* The method is called by a ProtocolProvider implementation whenever
* a change in the registration state of the corresponding provider had
* occurred.
* @param evt ProviderStatusChangeEvent the event describing the status
* change.
*/
public void registrationStateChanged(RegistrationStateChangeEvent evt)
{
if(evt.getNewState() == RegistrationState.REGISTERED)
{
this.parentProvider.getDiscoveryManager()
.getCapsManager().addUserCapsNodeListener(this);
}
}
/**
* Caps for user has been changed.
* @param user the user (full JID)
* @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
@Override
public void userCapsNodeAdded(String user, ArrayList<String> fullJids,
String node, boolean online)
{
updateMobileIndicatorUsingCaps(user);
}
/**
* Caps for user has been changed.
* @param user the user (full JID)
* @param fullJids a list of all resources of the user (full JIDs)
* @param node the entity caps node#ver
* @param online indicates if the user for which we're notified is online
*/
@Override
public void userCapsNodeRemoved(String user, ArrayList<String> fullJids,
String node, boolean online)
{
updateMobileIndicatorUsingCaps(user);
}
/**
* Update mobile indicator for contact, searching in contact caps.
* @param user the contact address with or without resource.
*/
private void updateMobileIndicatorUsingCaps(String user)
{
ContactJabberImpl contact =
ssclCallback.findContactById(StringUtils.parseBareAddress(user));
if(contact == null)
return;
// 1. Find most connected resources and if all are mobile
int currentMostConnectedStatus = 0;
List<ContactResource> mostAvailableResources =
new ArrayList<ContactResource>();
for(Map.Entry<String, ContactResourceJabberImpl> resEntry
: contact.getResourcesMap().entrySet())
{
ContactResourceJabberImpl res = resEntry.getValue();
if(!res.getPresenceStatus().isOnline())
continue;
// update the mobile indicator of connected resource,
// as caps have been updated
boolean oldIndicator = res.isMobile();
res.setMobile(isMobileResource(
res.getResourceName(), res.getFullJid()));
if(oldIndicator != res.isMobile())
{
contact.fireContactResourceEvent(
new ContactResourceEvent(
contact, res, ContactResourceEvent.RESOURCE_MODIFIED));
}
int status = res.getPresenceStatus().getStatus();
if(status > currentMostConnectedStatus)
{
if(currentMostConnectedStatus != status)
mostAvailableResources.clear();
currentMostConnectedStatus = status;
mostAvailableResources.add(res);
}
}
// check whether all are mobile
boolean allMobile = false;
for(ContactResource res : mostAvailableResources)
{
if(res.isMobile())
allMobile = true;
else
{
allMobile = false;
break;
}
}
if(mostAvailableResources.size() > 0)
contact.setMobile(allMobile);
else
contact.setMobile(false);
}
/**
* Checks whether <tt>value</tt> starts
* one of the <tt>checkStrs</> Strings.
* @param value the value to check
* @param checkStrs an array of strings we are searching for.
* @return <tt>true</tt> if <tt>value</tt> starts one of the Strings.
*/
private static boolean startsWithStrings(String value, String[] checkStrs)
{
for(String str : checkStrs)
{
if(str.length() > 0 && value.startsWith(str))
return true;
}
return false;
}
/**
* Checks whether <tt>value</tt> contains
* one of the <tt>checkStrs</> Strings.
* @param value the value to check
* @param checkStrs an array of strings we are searching for.
* @return <tt>true</tt> if <tt>value</tt> contains one of the Strings.
*/
private static boolean containsStrings(String value, String[] checkStrs)
{
for(String str : checkStrs)
{
if(str.length() > 0 && value.contains(str))
return true;
}
return false;
}
}