org.matrix.androidsdk.util.EventDisplay.java Source code

Java tutorial

Introduction

Here is the source code for org.matrix.androidsdk.util.EventDisplay.java

Source

/* 
 * Copyright 2016 OpenMarket 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.util;

import android.content.Context;
import android.graphics.Typeface;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import org.matrix.androidsdk.util.Log;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import org.matrix.androidsdk.R;
import org.matrix.androidsdk.call.MXCallsManager;
import org.matrix.androidsdk.crypto.MXCryptoError;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.EventContent;
import org.matrix.androidsdk.rest.model.Message;
import org.matrix.androidsdk.rest.model.RedactedBecause;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.RoomThirdPartyInvite;

/**
 * Class helper to stringify an event
 */
public class EventDisplay {

    private static final String LOG_TAG = "EventDisplay";

    // members
    private final Event mEvent;
    private final Context mContext;
    private final RoomState mRoomState;
    private boolean mPrependAuthor;

    // let the application defines if the redacted events must be displayed
    public static boolean mDisplayRedactedEvents = false;

    // constructor
    public EventDisplay(Context context, Event event, RoomState roomState) {
        mContext = context.getApplicationContext();
        mEvent = event;
        mRoomState = roomState;
    }

    /**
     * <p>Prepend the text with the author's name if they have not been mentioned in the text.</p>
     * This will prepend text messages with the author's name. This will NOT prepend things like
     * emote, room topic changes, etc which already mention the author's name in the message.
     * @param prepend true to prepend the message author.
     */
    public void setPrependMessagesWithAuthor(boolean prepend) {
        mPrependAuthor = prepend;
    }

    /**
     * Compute an "human readable" name for an user Id.
     * @param userId the user id
     * @param roomState the room state
     * @return the user display name
     */
    private static String getUserDisplayName(String userId, RoomState roomState) {
        if (null != roomState) {
            return roomState.getMemberName(userId);
        } else {
            return userId;
        }
    }

    /**
     * Stringify the linked event.
     * @return The text or null if it isn't possible.
     */
    public CharSequence getTextualDisplay() {
        return getTextualDisplay(null);
    }

    /**
     * Stringify the linked event.
     * @param displayNameColor the display name highlighted color.
     * @return The text or null if it isn't possible.
     */
    public CharSequence getTextualDisplay(Integer displayNameColor) {
        CharSequence text = null;

        try {
            JsonObject jsonEventContent = mEvent.getContentAsJsonObject();

            String userDisplayName = getUserDisplayName(mEvent.getSender(), mRoomState);
            String eventType = mEvent.getType();

            if (mEvent.isCallEvent()) {
                if (Event.EVENT_TYPE_CALL_INVITE.equals(eventType)) {
                    boolean isVideo = false;
                    // detect call type from the sdp
                    try {
                        JsonObject offer = jsonEventContent.get("offer").getAsJsonObject();
                        JsonElement sdp = offer.get("sdp");
                        String sdpValue = sdp.getAsString();
                        isVideo = sdpValue.contains("m=video");
                    } catch (Exception e) {
                        Log.e(LOG_TAG, "getTextualDisplay : " + e.getLocalizedMessage());
                    }

                    if (isVideo) {
                        return mContext.getString(R.string.notice_placed_video_call, userDisplayName);
                    } else {
                        return mContext.getString(R.string.notice_placed_voice_call, userDisplayName);
                    }
                } else if (Event.EVENT_TYPE_CALL_ANSWER.equals(eventType)) {
                    return mContext.getString(R.string.notice_answered_call, userDisplayName);
                } else if (Event.EVENT_TYPE_CALL_HANGUP.equals(eventType)) {
                    return mContext.getString(R.string.notice_ended_call, userDisplayName);
                } else {
                    return eventType;
                }
            } else if (Event.EVENT_TYPE_STATE_HISTORY_VISIBILITY.equals(eventType)) {
                CharSequence subpart;
                String historyVisibility = (null != jsonEventContent.get("history_visibility"))
                        ? jsonEventContent.get("history_visibility").getAsString()
                        : RoomState.HISTORY_VISIBILITY_SHARED;

                if (TextUtils.equals(historyVisibility, RoomState.HISTORY_VISIBILITY_SHARED)) {
                    subpart = mContext.getString(R.string.notice_room_visibility_shared);
                } else if (TextUtils.equals(historyVisibility, RoomState.HISTORY_VISIBILITY_INVITED)) {
                    subpart = mContext.getString(R.string.notice_room_visibility_invited);
                } else if (TextUtils.equals(historyVisibility, RoomState.HISTORY_VISIBILITY_JOINED)) {
                    subpart = mContext.getString(R.string.notice_room_visibility_joined);
                } else if (TextUtils.equals(historyVisibility, RoomState.HISTORY_VISIBILITY_WORLD_READABLE)) {
                    subpart = mContext.getString(R.string.notice_room_visibility_world_readable);
                } else {
                    subpart = mContext.getString(R.string.notice_room_visibility_unknown, historyVisibility);
                }

                text = mContext.getString(R.string.notice_made_future_room_visibility, userDisplayName, subpart);
            } else if (Event.EVENT_TYPE_RECEIPT.equals(eventType)) {
                // the read receipt should not be displayed
                text = "Read Receipt";
            } else if (Event.EVENT_TYPE_MESSAGE.equals(eventType)) {
                String msgtype = (null != jsonEventContent.get("msgtype"))
                        ? jsonEventContent.get("msgtype").getAsString()
                        : "";

                // all m.room.message events should support the 'body' key fallback, so use it.
                text = jsonEventContent.get("body") == null ? null : jsonEventContent.get("body").getAsString();

                // check for html formatting
                if (jsonEventContent.has("formatted_body") && jsonEventContent.has("format")) {
                    String format = jsonEventContent.getAsJsonPrimitive("format").getAsString();
                    if (Message.FORMAT_MATRIX_HTML.equals(format)) {
                        String htmlBody = jsonEventContent.getAsJsonPrimitive("formatted_body").getAsString();

                        // some markers are not supported so fallback on an ascii display until to find the right way to manage them
                        // an issue has been created https://github.com/vector-im/vector-android/issues/38
                        if (!TextUtils.isEmpty(htmlBody) && !htmlBody.contains("<ol>")
                                && !htmlBody.contains("<li>")) {
                            text = Html
                                    .fromHtml(jsonEventContent.getAsJsonPrimitive("formatted_body").getAsString());
                        }
                    }
                }

                // avoid empty image name
                if (TextUtils.equals(msgtype, Message.MSGTYPE_IMAGE) && TextUtils.isEmpty(text)) {
                    text = mContext.getString(R.string.summary_user_sent_image, userDisplayName);
                } else if (TextUtils.equals(msgtype, Message.MSGTYPE_EMOTE)) {
                    text = "* " + userDisplayName + " " + text;
                } else if (TextUtils.isEmpty(text)) {
                    text = "";
                } else if (mPrependAuthor) {
                    text = new SpannableStringBuilder(
                            mContext.getString(R.string.summary_message, userDisplayName, text));

                    if (null != displayNameColor) {
                        ((SpannableStringBuilder) text).setSpan(new ForegroundColorSpan(displayNameColor), 0,
                                userDisplayName.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                        ((SpannableStringBuilder) text).setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0,
                                userDisplayName.length() + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    }
                }
            } else if (Event.EVENT_TYPE_MESSAGE_ENCRYPTION.equals(eventType)) {
                text = mContext.getString(R.string.notice_end_to_end, userDisplayName,
                        mEvent.getWireEventContent().algorithm);
            } else if (Event.EVENT_TYPE_MESSAGE_ENCRYPTED.equals(eventType)) {
                // don't display
                if (mEvent.isRedacted()) {
                    String redactedInfo = EventDisplay.getRedactionMessage(mContext, mEvent, mRoomState);

                    if (TextUtils.isEmpty(redactedInfo)) {
                        return null;
                    } else {
                        return redactedInfo;
                    }
                } else {
                    String message = null;

                    if (null != mEvent.getCryptoError()) {
                        String errorDescription;

                        MXCryptoError error = mEvent.getCryptoError();

                        if (TextUtils.equals(error.errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) {
                            errorDescription = mContext.getResources()
                                    .getString(R.string.notice_crypto_error_unkwown_inbound_session_id);
                        } else {
                            errorDescription = error.getLocalizedMessage();
                        }

                        message = mContext.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription);
                    }

                    if (TextUtils.isEmpty(message)) {
                        message = mContext.getString(R.string.encrypted_message);
                    }

                    SpannableString spannableStr = new SpannableString(message);
                    spannableStr.setSpan(new android.text.style.StyleSpan(Typeface.ITALIC), 0, message.length(),
                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    text = spannableStr;
                }
            } else if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(eventType)) {
                String topic = jsonEventContent.getAsJsonPrimitive("topic").getAsString();

                if (mEvent.isRedacted()) {
                    String redactedInfo = EventDisplay.getRedactionMessage(mContext, mEvent, mRoomState);

                    if (TextUtils.isEmpty(redactedInfo)) {
                        return null;
                    }

                    topic = redactedInfo;
                }

                if (!TextUtils.isEmpty(topic)) {
                    text = mContext.getString(R.string.notice_topic_changed, userDisplayName, topic);
                } else {
                    text = mContext.getString(R.string.notice_room_topic_removed, userDisplayName);
                }
            } else if (Event.EVENT_TYPE_STATE_ROOM_NAME.equals(eventType)) {
                String roomName = jsonEventContent.getAsJsonPrimitive("name").getAsString();

                if (mEvent.isRedacted()) {
                    String redactedInfo = EventDisplay.getRedactionMessage(mContext, mEvent, mRoomState);

                    if (TextUtils.isEmpty(redactedInfo)) {
                        return null;
                    }

                    roomName = redactedInfo;
                }

                if (!TextUtils.isEmpty(roomName)) {
                    text = mContext.getString(R.string.notice_room_name_changed, userDisplayName, roomName);
                } else {
                    text = mContext.getString(R.string.notice_room_name_removed, userDisplayName);
                }
            } else if (Event.EVENT_TYPE_STATE_ROOM_THIRD_PARTY_INVITE.equals(eventType)) {
                RoomThirdPartyInvite invite = JsonUtils.toRoomThirdPartyInvite(mEvent.getContent());
                String displayName = invite.display_name;

                if (mEvent.isRedacted()) {
                    String redactedInfo = EventDisplay.getRedactionMessage(mContext, mEvent, mRoomState);

                    if (TextUtils.isEmpty(redactedInfo)) {
                        return null;
                    }

                    displayName = redactedInfo;
                }

                text = mContext.getString(R.string.notice_room_third_party_invite, userDisplayName, displayName);
            } else if (Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(eventType)) {
                text = getMembershipNotice(mContext, mEvent, mRoomState);
            }
        } catch (Exception e) {
            Log.e(LOG_TAG, "getTextualDisplay() " + e.getLocalizedMessage());
        }

        return text;
    }

    /**
     * Compute the redact text for an event.
     * @param context the context
     * @param event the event
     * @param roomState the room state
     * @return the redacted event text
     */
    public static String getRedactionMessage(Context context, Event event, RoomState roomState) {
        // test if the redacted event must be displayed.
        if (!mDisplayRedactedEvents) {
            return null;
        }

        // Check first whether the event has been redacted
        String redactedInfo = null;

        if (event.isRedacted() && (null != roomState)) {
            RedactedBecause redactedBecause = event.unsigned.redacted_because;
            String redactedBy = redactedBecause.sender;
            String redactedReason = null;

            if (null != redactedBecause.content) {
                redactedReason = redactedBecause.content.reason;
            }

            if (!TextUtils.isEmpty(redactedReason)) {
                if (!TextUtils.isEmpty(redactedBy)) {
                    redactedBy = context.getString(R.string.notice_event_redacted_by, redactedBy)
                            + context.getString(R.string.notice_event_redacted_reason, redactedReason);
                } else {
                    redactedBy = context.getString(R.string.notice_event_redacted_reason, redactedReason);
                }
            } else if (!TextUtils.isEmpty(redactedBy)) {
                redactedBy = context.getString(R.string.notice_event_redacted_by, redactedBy);
            }

            redactedInfo = context.getString(R.string.notice_event_redacted, redactedBy);
        }

        return redactedInfo;
    }

    /**
     * Compute the sender display name
     * @param event the event
     * @param eventContent the event content
     * @param roomState the room state
     * @return the "human readable" display name
     */
    private static String senderDisplayNameForEvent(Event event, EventContent eventContent,
            EventContent prevEventContent, RoomState roomState) {
        String senderDisplayName = event.getSender();

        if (!event.isRedacted()) {
            if (null != roomState) {
                // Consider first the current display name defined in provided room state (Note: this room state is supposed to not take the new event into account)
                senderDisplayName = roomState.getMemberName(event.getSender());
            }

            // Check whether this sender name is updated by the current event (This happens in case of new joined member)
            if ((null != eventContent) && TextUtils.equals(RoomMember.MEMBERSHIP_JOIN, eventContent.membership)) {
                // detect if it is displayname update
                // a display name update is detected when the previous state was join and there was a displayname
                if (!TextUtils.isEmpty(eventContent.displayname) || ((null != prevEventContent)
                        && TextUtils.equals(RoomMember.MEMBERSHIP_JOIN, prevEventContent.membership)
                        && !TextUtils.isEmpty(prevEventContent.displayname))) {
                    senderDisplayName = eventContent.displayname;
                }
            }
        }

        return senderDisplayName;
    }

    /**
     * Build a membership notice text from its dedicated event.
     * @param context the context.
     * @param event the event.
     * @param roomState the room state.
     * @return the membership text.
     */
    public static String getMembershipNotice(Context context, Event event, RoomState roomState) {
        JsonObject content = event.getContentAsJsonObject();

        // don't support redacted membership event
        if ((null == content) || (content.entrySet().size() == 0)) {
            return null;
        }

        EventContent eventContent = JsonUtils.toEventContent(event.getContentAsJsonObject());
        EventContent prevEventContent = event.getPrevContent();

        String senderDisplayName = senderDisplayNameForEvent(event, eventContent, prevEventContent, roomState);
        String prevUserDisplayName = null;

        String prevMembership = null;

        if (null != prevEventContent) {
            prevMembership = prevEventContent.membership;
        }

        if ((null != prevEventContent)) {
            prevUserDisplayName = prevEventContent.displayname;
        }

        // use by default the provided display name
        String targetDisplayName = eventContent.displayname;

        // if it is not provided, use the stateKey value
        // and try to retrieve a valid display name
        if (null == targetDisplayName) {
            targetDisplayName = event.stateKey;
            if ((null != targetDisplayName) && (null != roomState) && !event.isRedacted()) {
                targetDisplayName = roomState.getMemberName(targetDisplayName);
            }
        }

        // Check whether the sender has updated his profile (the membership is then unchanged)
        if (TextUtils.equals(prevMembership, eventContent.membership)) {
            String redactedInfo = EventDisplay.getRedactionMessage(context, event, roomState);

            // Is redacted event?
            if (event.isRedacted()) {

                // Here the event is ignored (no display)
                if (null == redactedInfo) {
                    return null;
                }

                return context.getString(R.string.notice_profile_change_redacted, senderDisplayName, redactedInfo);
            } else {
                String displayText = "";

                if (!TextUtils.equals(senderDisplayName, prevUserDisplayName)) {
                    if (TextUtils.isEmpty(prevUserDisplayName)) {
                        if (!TextUtils.equals(event.getSender(), senderDisplayName)) {
                            displayText = context.getString(R.string.notice_display_name_set, event.getSender(),
                                    senderDisplayName);
                        }
                    } else if (TextUtils.isEmpty(senderDisplayName)) {
                        displayText = context.getString(R.string.notice_display_name_removed, event.getSender(),
                                prevUserDisplayName);
                    } else {
                        displayText = context.getString(R.string.notice_display_name_changed_from,
                                event.getSender(), prevUserDisplayName, senderDisplayName);
                    }
                }

                // Check whether the avatar has been changed
                String avatar = eventContent.avatar_url;
                String prevAvatar = null;

                if (null != prevEventContent) {
                    prevAvatar = prevEventContent.avatar_url;
                }

                if (!TextUtils.equals(prevAvatar, avatar)) {
                    if (!TextUtils.isEmpty(displayText)) {
                        displayText = displayText + " " + context.getString(R.string.notice_avatar_changed_too);
                    } else {
                        displayText = context.getString(R.string.notice_avatar_url_changed, senderDisplayName);
                    }
                }

                return displayText;
            }
        } else if (RoomMember.MEMBERSHIP_INVITE.equals(eventContent.membership)) {
            if (null != eventContent.third_party_invite) {
                return context.getString(R.string.notice_room_third_party_registered_invite, targetDisplayName,
                        eventContent.third_party_invite.display_name);
            } else {
                String selfUserId = null;

                if ((null != roomState) && (null != roomState.getDataHandler())) {
                    selfUserId = roomState.getDataHandler().getUserId();
                }

                if (TextUtils.equals(event.stateKey, selfUserId)) {
                    return context.getString(R.string.notice_room_invite_you, senderDisplayName);
                }

                if (null == event.stateKey) {
                    return context.getString(R.string.notice_room_invite_no_invitee, senderDisplayName);
                }

                // conference call case
                if (targetDisplayName.equals(MXCallsManager.getConferenceUserId(event.roomId))) {
                    return context.getString(R.string.notice_requested_voip_conference, senderDisplayName);
                }

                return context.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName);
            }
        } else if (RoomMember.MEMBERSHIP_JOIN.equals(eventContent.membership)) {
            // conference call case
            if (TextUtils.equals(event.sender, MXCallsManager.getConferenceUserId(event.roomId))) {
                return context.getString(R.string.notice_voip_started);
            }

            return context.getString(R.string.notice_room_join, senderDisplayName);
        } else if (RoomMember.MEMBERSHIP_LEAVE.equals(eventContent.membership)) {
            // conference call case
            if (TextUtils.equals(event.sender, MXCallsManager.getConferenceUserId(event.roomId))) {
                return context.getString(R.string.notice_voip_finished);
            }

            // 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
            if (TextUtils.equals(event.getSender(), event.stateKey)) {
                if ((null != prevEventContent)
                        && TextUtils.equals(prevEventContent.membership, RoomMember.MEMBERSHIP_INVITE)) {
                    return context.getString(R.string.notice_room_reject, senderDisplayName);
                } else {

                    // use the latest known displayname
                    if ((null == eventContent.displayname) && (null != prevUserDisplayName)) {
                        senderDisplayName = prevUserDisplayName;
                    }

                    return context.getString(R.string.notice_room_leave, senderDisplayName);
                }

            } else if (null != prevMembership) {
                if (prevMembership.equals(RoomMember.MEMBERSHIP_INVITE)) {
                    return context.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName);
                } else if (prevMembership.equals(RoomMember.MEMBERSHIP_JOIN)) {
                    return context.getString(R.string.notice_room_kick, senderDisplayName, targetDisplayName);
                } else if (prevMembership.equals(RoomMember.MEMBERSHIP_BAN)) {
                    return context.getString(R.string.notice_room_unban, senderDisplayName, targetDisplayName);
                }
            }
        } else if (RoomMember.MEMBERSHIP_BAN.equals(eventContent.membership)) {
            return context.getString(R.string.notice_room_ban, senderDisplayName, targetDisplayName);
        } else {
            // eh?
            Log.e(LOG_TAG, "Unknown membership: " + eventContent.membership);
        }
        return null;
    }
}