/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data.roster; import android.support.annotation.Nullable; import com.xabber.android.R; import com.xabber.android.data.Application; import com.xabber.android.data.NetworkException; import com.xabber.android.data.OnLoadListener; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.account.StatusMode; import com.xabber.android.data.account.listeners.OnAccountDisabledListener; import com.xabber.android.data.connection.ConnectionItem; import com.xabber.android.data.connection.StanzaSender; import com.xabber.android.data.connection.listeners.OnPacketListener; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.extension.avatar.AvatarManager; import com.xabber.android.data.extension.capability.CapabilitiesManager; import com.xabber.android.data.extension.muc.MUCManager; import com.xabber.android.data.extension.muc.Occupant; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.notification.EntityNotificationProvider; import com.xabber.android.data.notification.NotificationManager; import com.xabber.xmpp.vcardupdate.VCardUpdate; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.parts.Resourcepart; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Process contact's presence information. * * @author alexander.ivanov */ public class PresenceManager implements OnLoadListener, OnAccountDisabledListener, OnPacketListener { private static PresenceManager instance; private final EntityNotificationProvider<SubscriptionRequest> subscriptionRequestProvider; /** * List of account with requested subscriptions for auto accept incoming * subscription request. */ private final HashMap<AccountJid, Set<UserJid>> requestedSubscriptions; public static PresenceManager getInstance() { if (instance == null) { instance = new PresenceManager(); } return instance; } private PresenceManager() { subscriptionRequestProvider = new EntityNotificationProvider<>(R.drawable.ic_stat_add_circle); requestedSubscriptions = new HashMap<>(); } @Override public void onLoad() { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { onLoaded(); } }); } private void onLoaded() { NotificationManager.getInstance().registerNotificationProvider(subscriptionRequestProvider); } /** * @return <code>null</code> can be returned. */ public SubscriptionRequest getSubscriptionRequest(AccountJid account, UserJid user) { return subscriptionRequestProvider.get(account, user); } /** * Requests subscription to the contact. * * @throws NetworkException */ public void requestSubscription(AccountJid account, UserJid user) throws NetworkException { Presence packet = new Presence(Presence.Type.subscribe); packet.setTo(user.getJid()); StanzaSender.sendStanza(account, packet); Set<UserJid> set = requestedSubscriptions.get(account); if (set == null) { set = new HashSet<>(); requestedSubscriptions.put(account, set); } set.add(user); } private void removeRequestedSubscription(AccountJid account, UserJid user) { Set<UserJid> set = requestedSubscriptions.get(account); if (set != null) { set.remove(user); } } /** * Accepts subscription request from the entity (share own presence). */ public void acceptSubscription(AccountJid account, UserJid user) throws NetworkException { Presence packet = new Presence(Presence.Type.subscribed); packet.setTo(user.getJid()); StanzaSender.sendStanza(account, packet); subscriptionRequestProvider.remove(account, user); removeRequestedSubscription(account, user); } /** * Discards subscription request from the entity (deny own presence * sharing). */ public void discardSubscription(AccountJid account, UserJid user) throws NetworkException { Presence packet = new Presence(Presence.Type.unsubscribed); packet.setTo(user.getJid()); StanzaSender.sendStanza(account, packet); subscriptionRequestProvider.remove(account, user); removeRequestedSubscription(account, user); } public boolean hasSubscriptionRequest(AccountJid account, UserJid bareAddress) { return getSubscriptionRequest(account, bareAddress) != null; } public StatusMode getStatusMode(AccountJid account, UserJid user) { final Occupant occupant = getOccupant(account, user); if (occupant != null) { return occupant.getStatusMode(); } return StatusMode.createStatusMode(RosterManager.getInstance().getPresence(account, user)); } /** * if contact is private MUC chat */ @Nullable private Occupant getOccupant(AccountJid account, UserJid user) { EntityBareJid userEntityBareJid = user.getJid().asEntityBareJidIfPossible(); if (userEntityBareJid == null) { return null; } Resourcepart resourcepart = user.getJid().getResourceOrNull(); if (resourcepart == null) { return null; } if (MUCManager.getInstance().hasRoom(account, userEntityBareJid)) { final Collection<Occupant> occupants = MUCManager.getInstance().getOccupants(account, userEntityBareJid); for (Occupant occupant : occupants) { if (occupant.getNickname().equals(resourcepart)) { return occupant; } } } return null; } public String getStatusText(AccountJid account, UserJid bareAddress) { final Occupant occupant = getOccupant(account, bareAddress); if (occupant != null) { return occupant.getStatusText(); } final Presence presence = RosterManager.getInstance().getPresence(account, bareAddress); if (presence == null) { return null; } else { return presence.getStatus(); } } public void onPresenceChanged(AccountJid account, Presence presence) { UserJid from; try { from = UserJid.from(presence.getFrom()); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); return; } if (presence.isAvailable()) { CapabilitiesManager.getInstance().onPresence(account, presence); } for (OnStatusChangeListener listener : Application.getInstance().getManagers(OnStatusChangeListener.class)) { listener.onStatusChanged(account, from, StatusMode.createStatusMode(presence), presence.getStatus()); } RosterContact rosterContact = RosterManager.getInstance().getRosterContact(account, from.getBareJid()); if (rosterContact != null) { ArrayList<RosterContact> rosterContacts = new ArrayList<>(); rosterContacts.add(rosterContact); for (OnRosterChangedListener listener : Application.getInstance().getManagers(OnRosterChangedListener.class)) { listener.onPresenceChanged(rosterContacts); } } RosterManager.onContactChanged(account, from); } @Override public void onAccountDisabled(AccountItem accountItem) { requestedSubscriptions.remove(accountItem.getAccount()); } /** * Sends new presence information. * * @throws NetworkException */ public void resendPresence(AccountJid account) throws NetworkException { sendVCardUpdatePresence(account, AvatarManager.getInstance().getHash(account.getFullJid().asBareJid())); } public void sendVCardUpdatePresence(AccountJid account, String hash) throws NetworkException { LogManager.i(this, "sendVCardUpdatePresence: " + account); AccountItem accountItem = AccountManager.getInstance().getAccount(account); if (accountItem == null) { return; } final Presence presence = accountItem.getPresence(); final VCardUpdate vCardUpdate = new VCardUpdate(); vCardUpdate.setPhotoHash(hash); presence.addExtension(vCardUpdate); StanzaSender.sendStanza(account, presence); } @Override public void onStanza(ConnectionItem connection, Stanza stanza) { if (!(connection instanceof AccountItem)) { return; } if (!(stanza instanceof Presence)) { return; } Presence presence = (Presence) stanza; UserJid from; try { from = UserJid.from(stanza.getFrom()); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); return; } if (presence.getType() == Presence.Type.subscribe) { AccountJid account = connection.getAccount(); // Subscription request Set<UserJid> set = requestedSubscriptions.get(account); if (set != null && set.contains(from)) { try { acceptSubscription(account, from); } catch (NetworkException e) { LogManager.exception(this, e); } subscriptionRequestProvider.remove(account, from); } else { subscriptionRequestProvider.add(new SubscriptionRequest(account, from), null); } } } public void onAuthorized(ConnectionItem connection) { try { resendPresence(connection.getAccount()); } catch (NetworkException e) { LogManager.exception(this, e); } } public static void sortPresencesByPriority(List<Presence> allPresences) { Collections.sort(allPresences, PresenceComparatorByPriority.INSTANCE); } }