/* * Copyright 2016 OpenMarket Ltd * Copyright 2017 Vector Creations 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 org.matrix.androidsdk.rest.model; import android.text.TextUtils; import org.matrix.androidsdk.MXDataHandler; import org.matrix.androidsdk.listeners.IMXEventListener; import org.matrix.androidsdk.listeners.MXEventListener; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; /** * Class representing a user. */ public class User implements java.io.Serializable { private static final long serialVersionUID = 5234056937639712713L; // the user presence values public static final String PRESENCE_ONLINE = "online"; public static final String PRESENCE_UNAVAILABLE = "unavailable"; public static final String PRESENCE_OFFLINE = "offline"; public static final String PRESENCE_FREE_FOR_CHAT = "free_for_chat"; public static final String PRESENCE_HIDDEN = "hidden"; // user fields provided by the server public String user_id; public String displayname; public String avatar_url; public String presence; public Boolean currently_active; public Long lastActiveAgo; public String statusMsg; // tell if the information has been refreshed private transient boolean mIsPresenceRefreshed; // Used to provide a more realistic last active time: // the last active ago time provided by the server + the time that has gone by since private long mLastPresenceTs; // Map to keep track of the listeners the client adds vs. the ones we actually register to the global data handler. // This is needed to find the right one when removing the listener. private transient Map<IMXEventListener, IMXEventListener> mEventListeners = new HashMap<>(); // data handler protected transient MXDataHandler mDataHandler; // events listeners list private transient ArrayList<IMXEventListener> mPendingListeners = new ArrayList<>(); // hash key to store the user in the file system; private Integer mStorageHashKey = null; // The user data can have been retrieved by a room member // The data can be partially invalid until a presence is received private boolean mIsRetrievedFromRoomMember = false; // avatar URLs setter / getter public String getAvatarUrl() { return avatar_url; } public void setAvatarUrl(String newAvatarUrl) { avatar_url = newAvatarUrl; } /** * Tells if this user has been created from a room member event */ public boolean isRetrievedFromRoomMember() { return mIsRetrievedFromRoomMember; } /** * Set that this user has been created from a room member. */ public void setRetrievedFromRoomMember() { mIsRetrievedFromRoomMember = true; } /** * Check if mEventListeners has been initialized before providing it. * The users are now serialized and the transient fields are not initialized. * * @return the events listener */ private Map<IMXEventListener, IMXEventListener> getEventListeners() { if (null == mEventListeners) { mEventListeners = new HashMap<>(); } return mEventListeners; } /** * Check if mPendingListeners has been initialized before providing it. * The users are now serialized and the transient fields are not initialized. * * @return the pending listener */ private ArrayList<IMXEventListener> getPendingListeners() { if (null == mPendingListeners) { mPendingListeners = new ArrayList<>(); } return mPendingListeners; } /** * @return the user hash key */ public int getStorageHashKey() { if (null == mStorageHashKey) { mStorageHashKey = Math.abs(user_id.hashCode() % 100); } return mStorageHashKey; } /** * @return true if the presence should be refreshed */ public boolean isPresenceObsolete() { return !mIsPresenceRefreshed || (null == presence); } /** * Clone an user into this instance * * @param user the user to clone. */ protected void clone(User user) { if (user != null) { user_id = user.user_id; displayname = user.displayname; avatar_url = user.avatar_url; presence = user.presence; currently_active = user.currently_active; lastActiveAgo = user.lastActiveAgo; statusMsg = user.statusMsg; mIsPresenceRefreshed = user.mIsPresenceRefreshed; mLastPresenceTs = user.mLastPresenceTs; mEventListeners = new HashMap<>(user.getEventListeners()); mDataHandler = user.mDataHandler; mPendingListeners = user.getPendingListeners(); } } /** * Create a deep copy of the current user. * * @return a deep copy of the current object */ public User deepCopy() { User copy = new User(); copy.clone(this); return copy; } /** * Tells if an user is active * * @return true if the user is active */ public boolean isActive() { return TextUtils.equals(presence, PRESENCE_ONLINE) || ((null != currently_active) && currently_active); } /** * Set the latest presence event time. * * @param ts the timestamp. */ public void setLatestPresenceTs(long ts) { mIsPresenceRefreshed = true; mLastPresenceTs = ts; } /** * @return the timestamp of the latest presence event. */ public long getLatestPresenceTs() { return mLastPresenceTs; } /** * Get the user's last active ago time by adding the one given by the server and the time since elapsed. * * @return how long ago the user was last active (in ms) */ public long getAbsoluteLastActiveAgo() { // sanity check if (null == lastActiveAgo) { return 0; } else { return System.currentTimeMillis() - (mLastPresenceTs - lastActiveAgo); } } /** * Set the event listener to send back events to. This is typically the DataHandler for dispatching the events to listeners. * * @param dataHandler should be the main data handler for dispatching back events to registered listeners. */ public void setDataHandler(MXDataHandler dataHandler) { mDataHandler = dataHandler; for (IMXEventListener listener : getPendingListeners()) { mDataHandler.addListener(listener); } } /** * Add an event listener to this room. Only events relative to the room will come down. * * @param eventListener the event listener to add */ public void addEventListener(final IMXEventListener eventListener) { // Create a global listener that we'll add to the data handler IMXEventListener globalListener = new MXEventListener() { @Override public void onPresenceUpdate(Event event, User user) { // Only pass event through for this user if (user.user_id.equals(user_id)) { eventListener.onPresenceUpdate(event, user); } } }; getEventListeners().put(eventListener, globalListener); // the handler could be set later if (null != mDataHandler) { mDataHandler.addListener(globalListener); } else { getPendingListeners().add(globalListener); } } /** * Remove an event listener. * * @param eventListener the event listener to remove */ public void removeEventListener(IMXEventListener eventListener) { if (null != mDataHandler) { mDataHandler.removeListener(getEventListeners().get(eventListener)); } else { getPendingListeners().remove(getEventListeners().get(eventListener)); } getEventListeners().remove(eventListener); } }