/*
* 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.gui.main.chat;
import java.io.*;
import java.net.*;
import javax.swing.*;
import net.java.sip.communicator.impl.gui.utils.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;
/**
* The single chat implementation of the <tt>ChatTransport</tt> interface that
* provides abstraction to protocol provider access.
*
* @author Yana Stamcheva
*/
public class MetaContactChatTransport
implements ChatTransport,
ContactPresenceStatusListener
{
/**
* The logger.
*/
private static final Logger logger
= Logger.getLogger(MetaContactChatTransport.class);
/**
* The parent <tt>ChatSession</tt>, where this transport is available.
*/
private final MetaContactChatSession parentChatSession;
/**
* The associated protocol <tt>Contact</tt>.
*/
private final Contact contact;
/**
* The resource associated with this contact.
*/
private ContactResource contactResource;
/**
* The protocol presence operation set associated with this transport.
*/
private final OperationSetPresence presenceOpSet;
/**
* The thumbnail default width.
*/
private static final int THUMBNAIL_WIDTH = 64;
/**
* The thumbnail default height.
*/
private static final int THUMBNAIL_HEIGHT = 64;
/**
* Indicates if only the resource name should be displayed.
*/
private boolean isDisplayResourceOnly = false;
/**
* Creates an instance of <tt>MetaContactChatTransport</tt> by specifying
* the parent <tt>chatSession</tt> and the <tt>contact</tt> associated with
* the transport.
*
* @param chatSession the parent <tt>ChatSession</tt>
* @param contact the <tt>Contact</tt> associated with this transport
*/
public MetaContactChatTransport(MetaContactChatSession chatSession,
Contact contact)
{
this(chatSession, contact, null, false);
}
/**
* Creates an instance of <tt>MetaContactChatTransport</tt> by specifying
* the parent <tt>chatSession</tt> and the <tt>contact</tt> associated with
* the transport.
*
* @param chatSession the parent <tt>ChatSession</tt>
* @param contact the <tt>Contact</tt> associated with this transport
* @param contactResource the <tt>ContactResource</tt> associated with the
* contact
* @param isDisplayResourceOnly indicates if only the resource name should
* be displayed
*/
public MetaContactChatTransport(MetaContactChatSession chatSession,
Contact contact,
ContactResource contactResource,
boolean isDisplayResourceOnly)
{
this.parentChatSession = chatSession;
this.contact = contact;
this.contactResource = contactResource;
this.isDisplayResourceOnly = isDisplayResourceOnly;
presenceOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetPresence.class);
if (presenceOpSet != null)
presenceOpSet.addContactPresenceStatusListener(this);
// checking this can be slow so make
// sure its out of our way
new Thread(new Runnable()
{
public void run()
{
checkImCaps();
}
}).start();
}
/**
* If sending im is supported check it for supporting html messages
* if a font is set.
* As it can be slow make sure its not on our way
*/
private void checkImCaps()
{
if (ConfigurationUtils.getChatDefaultFontFamily() != null
&& ConfigurationUtils.getChatDefaultFontSize() > 0)
{
OperationSetBasicInstantMessaging imOpSet
= contact.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class);
if(imOpSet != null)
imOpSet.isContentTypeSupported(
OperationSetBasicInstantMessaging.HTML_MIME_TYPE, contact);
}
}
/**
* Returns the contact associated with this transport.
*
* @return the contact associated with this transport
*/
public Contact getContact()
{
return contact;
}
/**
* Returns the contact address corresponding to this chat transport.
*
* @return The contact address corresponding to this chat transport.
*/
public String getName()
{
return contact.getAddress();
}
/**
* Returns the display name corresponding to this chat transport.
*
* @return The display name corresponding to this chat transport.
*/
public String getDisplayName()
{
return contact.getDisplayName();
}
/**
* Returns the resource name of this chat transport. This is for example the
* name of the user agent from which the contact is logged.
*
* @return The display name of this chat transport resource.
*/
public String getResourceName()
{
if (contactResource != null)
return contactResource.getResourceName();
return null;
}
public boolean isDisplayResourceOnly()
{
return isDisplayResourceOnly;
}
/**
* Returns the presence status of this transport.
*
* @return the presence status of this transport.
*/
public PresenceStatus getStatus()
{
if (contactResource != null)
return contactResource.getPresenceStatus();
else
return contact.getPresenceStatus();
}
/**
* Returns the <tt>ProtocolProviderService</tt>, corresponding to this chat
* transport.
*
* @return the <tt>ProtocolProviderService</tt>, corresponding to this chat
* transport.
*/
public ProtocolProviderService getProtocolProvider()
{
return contact.getProtocolProvider();
}
/**
* Returns <code>true</code> if this chat transport supports instant
* messaging, otherwise returns <code>false</code>.
*
* @return <code>true</code> if this chat transport supports instant
* messaging, otherwise returns <code>false</code>.
*/
public boolean allowsInstantMessage()
{
// First try to ask the capabilities operation set if such is
// available.
OperationSetContactCapabilities capOpSet = getProtocolProvider()
.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
{
if (capOpSet.getOperationSet(
contact, OperationSetBasicInstantMessaging.class) != null)
{
return true;
}
}
else if (contact.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class) != null)
return true;
return false;
}
/**
* Returns <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*
* @return <code>true</code> if this chat transport supports message
* corrections and false otherwise.
*/
public boolean allowsMessageCorrections()
{
OperationSetContactCapabilities capOpSet = getProtocolProvider()
.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
{
return capOpSet.getOperationSet(
contact, OperationSetMessageCorrection.class) != null;
}
else
{
return contact.getProtocolProvider().getOperationSet(
OperationSetMessageCorrection.class) != null;
}
}
/**
* Returns <code>true</code> if this chat transport supports sms
* messaging, otherwise returns <code>false</code>.
*
* @return <code>true</code> if this chat transport supports sms
* messaging, otherwise returns <code>false</code>.
*/
public boolean allowsSmsMessage()
{
// First try to ask the capabilities operation set if such is
// available.
OperationSetContactCapabilities capOpSet = getProtocolProvider()
.getOperationSet(OperationSetContactCapabilities.class);
if (capOpSet != null)
{
if (capOpSet.getOperationSet(
contact, OperationSetSmsMessaging.class) != null)
{
return true;
}
}
else if (contact.getProtocolProvider()
.getOperationSet(OperationSetSmsMessaging.class) != null)
return true;
return false;
}
/**
* Returns <code>true</code> if this chat transport supports typing
* notifications, otherwise returns <code>false</code>.
*
* @return <code>true</code> if this chat transport supports typing
* notifications, otherwise returns <code>false</code>.
*/
public boolean allowsTypingNotifications()
{
Object tnOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetTypingNotifications.class);
if (tnOpSet != null)
return true;
else
return false;
}
/**
* Returns <code>true</code> if this chat transport supports file transfer,
* otherwise returns <code>false</code>.
*
* @return <code>true</code> if this chat transport supports file transfer,
* otherwise returns <code>false</code>.
*/
public boolean allowsFileTransfer()
{
Object ftOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetFileTransfer.class);
if (ftOpSet != null)
return true;
else
return false;
}
/**
* Sends the given instant message through this chat transport,
* by specifying the mime type (html or plain text).
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @throws Exception if the send operation is interrupted
*/
public void sendInstantMessage( String message,
String mimeType)
throws Exception
{
// If this chat transport does not support instant messaging we do
// nothing here.
if (!allowsInstantMessage())
return;
OperationSetBasicInstantMessaging imOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class);
Message msg;
if (mimeType.equals(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)
&& imOpSet.isContentTypeSupported(
OperationSetBasicInstantMessaging.HTML_MIME_TYPE))
{
msg = imOpSet.createMessage(message,
OperationSetBasicInstantMessaging.HTML_MIME_TYPE, "utf-8", "");
}
else
{
msg = imOpSet.createMessage(message);
}
if (contactResource != null)
imOpSet.sendInstantMessage(contact, contactResource, msg);
else
imOpSet.sendInstantMessage(contact,
ContactResource.BASE_RESOURCE, msg);
}
/**
* Sends <tt>message</tt> as a message correction through this transport,
* specifying the mime type (html or plain text) and the id of the
* message to replace.
*
* @param message The message to send.
* @param mimeType The mime type of the message to send: text/html or
* text/plain.
* @param correctedMessageUID The ID of the message being corrected by
* this message.
*/
public void correctInstantMessage(String message, String mimeType,
String correctedMessageUID)
{
if (!allowsMessageCorrections())
{
return;
}
OperationSetMessageCorrection mcOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetMessageCorrection.class);
Message msg;
if (mimeType.equals(OperationSetBasicInstantMessaging.HTML_MIME_TYPE)
&& mcOpSet.isContentTypeSupported(
OperationSetBasicInstantMessaging.HTML_MIME_TYPE))
{
msg = mcOpSet.createMessage(message,
OperationSetBasicInstantMessaging.HTML_MIME_TYPE,
"utf-8", "");
}
else
{
msg = mcOpSet.createMessage(message);
}
mcOpSet.correctMessage(
contact, contactResource, msg, correctedMessageUID);
}
/**
* Determines whether this chat transport supports the supplied content type
*
* @param contentType the type we want to check
* @return <tt>true</tt> if the chat transport supports it and
* <tt>false</tt> otherwise.
*/
public boolean isContentTypeSupported(String contentType)
{
OperationSetBasicInstantMessaging imOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class);
if(imOpSet != null)
return imOpSet.isContentTypeSupported(contentType);
else
return false;
}
/**
* Sends the given sms message trough this chat transport.
*
* @param phoneNumber phone number of the destination
* @param messageText The message to send.
* @throws Exception if the send operation is interrupted
*/
public void sendSmsMessage(String phoneNumber, String messageText)
throws Exception
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsSmsMessage())
return;
SMSManager.sendSMS(
contact.getProtocolProvider(),
phoneNumber,
messageText);
}
/**
* Whether a dialog need to be opened so the user can enter the destination
* number.
* @return <tt>true</tt> if dialog needs to be open.
*/
public boolean askForSMSNumber()
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsSmsMessage())
return false;
OperationSetSmsMessaging smsOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetSmsMessaging.class);
return smsOpSet.askForNumber(contact);
}
/**
* Sends the given sms message trough this chat transport.
*
* @param message the message to send
* @throws Exception if the send operation is interrupted
*/
public void sendSmsMessage(String message)
throws Exception
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsSmsMessage())
return;
SMSManager.sendSMS(contact, message);
}
/**
* Sends a typing notification state.
*
* @param typingState the typing notification state to send
* @return the result of this operation. One of the TYPING_NOTIFICATION_XXX
* constants defined in this class
*/
public int sendTypingNotification(int typingState)
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsTypingNotifications())
return -1;
ProtocolProviderService protocolProvider
= contact.getProtocolProvider();
OperationSetTypingNotifications tnOperationSet
= protocolProvider
.getOperationSet(OperationSetTypingNotifications.class);
// if protocol is not registered or contact is offline don't
// try to send typing notifications
if(protocolProvider.isRegistered()
&& contact.getPresenceStatus().getStatus()
>= PresenceStatus.ONLINE_THRESHOLD)
{
try
{
tnOperationSet.sendTypingNotification(
contact, typingState);
return ChatPanel.TYPING_NOTIFICATION_SUCCESSFULLY_SENT;
}
catch (Exception ex)
{
logger.error("Failed to send typing notifications.", ex);
return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
}
}
return ChatPanel.TYPING_NOTIFICATION_SEND_FAILED;
}
/**
* Sends the given file through this chat transport file transfer operation
* set.
* @param file the file to send
* @return the <tt>FileTransfer</tt> object charged to transfer the file
* @throws Exception if anything goes wrong
*/
public FileTransfer sendFile(File file)
throws Exception
{
return sendFile(file, false);
}
/**
* Sends the given file through this chat transport file transfer operation
* set.
* @param file the file to send
* @return the <tt>FileTransfer</tt> object charged to transfer the file
* @throws Exception if anything goes wrong
*/
private FileTransfer sendFile(File file, boolean isMultimediaMessage)
throws Exception
{
// If this chat transport does not support instant messaging we do
// nothing here.
if (!allowsFileTransfer())
return null;
if (FileUtils.isImage(file.getName()))
{
// Create a thumbnailed file if possible.
OperationSetThumbnailedFileFactory tfOpSet
= contact
.getProtocolProvider()
.getOperationSet(
OperationSetThumbnailedFileFactory.class);
if (tfOpSet != null)
{
byte[] thumbnail = getFileThumbnail(file);
if (thumbnail != null && thumbnail.length > 0)
{
file = tfOpSet.createFileWithThumbnail(
file, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT,
"image/png", thumbnail);
}
}
}
if(isMultimediaMessage)
{
OperationSetSmsMessaging smsOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetSmsMessaging.class);
if(smsOpSet == null)
return null;
return smsOpSet.sendMultimediaFile(contact, file);
}
else
{
OperationSetFileTransfer ftOpSet = contact.getProtocolProvider()
.getOperationSet(OperationSetFileTransfer.class);
return ftOpSet.sendFile(contact, file);
}
}
/**
* Sends the given SMS multimedia message trough this chat transport,
* leaving the transport to choose the destination.
*
* @param file the file to send
* @throws Exception if the send doesn't succeed
*/
public FileTransfer sendMultimediaFile(File file)
throws Exception
{
return sendFile(file, true);
}
/**
* Returns the maximum file length supported by the protocol in bytes.
* @return the file length that is supported.
*/
public long getMaximumFileLength()
{
OperationSetFileTransfer ftOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetFileTransfer.class);
return ftOpSet.getMaximumFileLength();
}
public void inviteChatContact(String contactAddress, String reason) {}
/**
* Returns the parent session of this chat transport. A <tt>ChatSession</tt>
* could contain more than one transports.
*
* @return the parent session of this chat transport
*/
public ChatSession getParentChatSession()
{
return parentChatSession;
}
/**
* Adds an SMS message listener to this chat transport.
* @param l The message listener to add.
*/
public void addSmsMessageListener(MessageListener l)
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsSmsMessage())
return;
OperationSetSmsMessaging smsOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetSmsMessaging.class);
smsOpSet.addMessageListener(l);
}
/**
* Adds an instant message listener to this chat transport.
* @param l The message listener to add.
*/
public void addInstantMessageListener(MessageListener l)
{
// If this chat transport does not support instant messaging we do
// nothing here.
if (!allowsInstantMessage())
return;
OperationSetBasicInstantMessaging imOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class);
imOpSet.addMessageListener(l);
}
/**
* Removes the given sms message listener from this chat transport.
* @param l The message listener to remove.
*/
public void removeSmsMessageListener(MessageListener l)
{
// If this chat transport does not support sms messaging we do
// nothing here.
if (!allowsSmsMessage())
return;
OperationSetSmsMessaging smsOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetSmsMessaging.class);
smsOpSet.removeMessageListener(l);
}
/**
* Removes the instant message listener from this chat transport.
* @param l The message listener to remove.
*/
public void removeInstantMessageListener(MessageListener l)
{
// If this chat transport does not support instant messaging we do
// nothing here.
if (!allowsInstantMessage())
return;
OperationSetBasicInstantMessaging imOpSet
= contact
.getProtocolProvider()
.getOperationSet(OperationSetBasicInstantMessaging.class);
imOpSet.removeMessageListener(l);
}
/**
* Indicates that a contact has changed its status.
* @param evt The presence event containing information about the
* contact status change.
*/
public void contactPresenceStatusChanged(
ContactPresenceStatusChangeEvent evt)
{
if (evt.getSourceContact().equals(contact)
&& !evt.getOldStatus().equals(evt.getNewStatus())
&& contactResource == null) // If the contact source is set then the
// status will be updated from the
// MetaContactChatSession.
{
this.updateContactStatus();
}
}
/**
* Updates the status of this contact with the new given status.
*/
private void updateContactStatus()
{
// Update the status of the given contact in the "send via" selector
// box.
parentChatSession.getChatSessionRenderer()
.updateChatTransportStatus(this);
}
/**
* Removes all previously added listeners.
*/
public void dispose()
{
if (presenceOpSet != null)
presenceOpSet.removeContactPresenceStatusListener(this);
}
/**
* Returns the descriptor of this chat transport.
* @return the descriptor of this chat transport
*/
public Object getDescriptor()
{
return contact;
}
/**
* Sets the icon for the given file.
*
* @param file the file to set an icon for
* @return the byte array containing the thumbnail
*/
private byte[] getFileThumbnail(File file)
{
byte[] bytes = null;
if (FileUtils.isImage(file.getName()))
{
try
{
ImageIcon image = new ImageIcon(file.toURI().toURL());
int width = image.getIconWidth();
int height = image.getIconHeight();
if (width > THUMBNAIL_WIDTH)
width = THUMBNAIL_WIDTH;
if (height > THUMBNAIL_HEIGHT)
height = THUMBNAIL_HEIGHT;
bytes
= ImageUtils
.getScaledInstanceInBytes(
image.getImage(),
width,
height);
}
catch (MalformedURLException e)
{
if (logger.isDebugEnabled())
logger.debug("Could not locate image.", e);
}
}
return bytes;
}
}