/* * Copyright 2014 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.util.Log; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import org.matrix.androidsdk.crypto.MXCryptoError; import org.matrix.androidsdk.db.MXMediasCache; import org.matrix.androidsdk.util.JsonUtils; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; /** * Generic event class with all possible fields for events. */ public class Event implements Externalizable { private static final String LOG_TAG = "Event"; private static final long serialVersionUID = -1431845331022808337L; public enum SentState { UNSENT, // the event has not been sent ENCRYPTING, // the event is encrypting SENDING, // the event is currently sending WAITING_RETRY, // the event is going to be resent asap SENT, // the event has been sent UNDELIVERABLE, // The event failed to be sent FAILED_UNKNOWN_DEVICES // the event failed to be sent because some unknown devices have been found while encrypting it } // when there is no more message to be paginated in a room // the server returns a null token. // defines by a non null one to ben able tp store it. public static final String PAGINATE_BACK_TOKEN_END = "PAGINATE_BACK_TOKEN_END"; public static final String EVENT_TYPE_PRESENCE = "m.presence"; public static final String EVENT_TYPE_MESSAGE = "m.room.message"; public static final String EVENT_TYPE_MESSAGE_ENCRYPTED = "m.room.encrypted"; public static final String EVENT_TYPE_MESSAGE_ENCRYPTION = "m.room.encryption"; public static final String EVENT_TYPE_FEEDBACK = "m.room.message.feedback"; public static final String EVENT_TYPE_TYPING = "m.typing"; public static final String EVENT_TYPE_REDACTION = "m.room.redaction"; public static final String EVENT_TYPE_RECEIPT = "m.receipt"; public static final String EVENT_TYPE_TAGS = "m.tag"; public static final String EVENT_TYPE_NEW_DEVICE = "m.new_device"; public static final String EVENT_TYPE_ROOM_KEY = "m.room_key"; // State events public static final String EVENT_TYPE_STATE_ROOM_NAME = "m.room.name"; public static final String EVENT_TYPE_STATE_ROOM_TOPIC = "m.room.topic"; public static final String EVENT_TYPE_STATE_ROOM_AVATAR = "m.room.avatar"; public static final String EVENT_TYPE_STATE_ROOM_MEMBER = "m.room.member"; public static final String EVENT_TYPE_STATE_ROOM_THIRD_PARTY_INVITE = "m.room.third_party_invite"; public static final String EVENT_TYPE_STATE_ROOM_CREATE = "m.room.create"; public static final String EVENT_TYPE_STATE_ROOM_JOIN_RULES = "m.room.join_rules"; public static final String EVENT_TYPE_STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"; public static final String EVENT_TYPE_STATE_ROOM_POWER_LEVELS = "m.room.power_levels"; public static final String EVENT_TYPE_STATE_ROOM_ALIASES = "m.room.aliases"; public static final String EVENT_TYPE_STATE_CANONICAL_ALIAS = "m.room.canonical_alias"; public static final String EVENT_TYPE_STATE_HISTORY_VISIBILITY = "m.room.history_visibility"; // call events public static final String EVENT_TYPE_CALL_INVITE = "m.call.invite"; public static final String EVENT_TYPE_CALL_CANDIDATES = "m.call.candidates"; public static final String EVENT_TYPE_CALL_ANSWER = "m.call.answer"; public static final String EVENT_TYPE_CALL_HANGUP = "m.call.hangup"; public static final long DUMMY_EVENT_AGE = Long.MAX_VALUE - 1; public String type; public transient JsonElement content = null; private String contentAsString = null; public transient JsonElement prev_content = null; private String prev_content_as_string = null; public String eventId; public String roomId; // former Sync V1 sender name public String userId; // Sync V2 sender name public String sender; public long originServerTs; public Long age; // Specific to state events public String stateKey; // Contains optional extra information about the event. public UnsignedData unsigned; // Specific to redaction public String redacts; // A subset of the state of the room at the time of the invite, if membership is invite public List<Event> invite_room_state; // store the exception triggered when unsent public Exception unsentException = null; public MatrixError unsentMatrixError = null; // sent state public SentState mSentState = SentState.SENT; // save the token to back paginate // the room history could have been reduced to save memory. // so store the token from each event. public String mToken; // The file cache uses the token as a pagination marker. // When the user paginates, the file cache paginate until to find X events or an event with a token. // This token must be used to perform a server catchup. public boolean mIsInternalPaginationToken; // store the linked matrix id private String mMatrixId; // the time raw offset (time zone management) private long mTimeZoneRawOffset = 0; private long getTimeZoneOffset() { return TimeZone.getDefault().getRawOffset(); } /** * Default constructor */ public Event() { type = null; content = null; prev_content = null; mIsInternalPaginationToken = false; userId = roomId = eventId = null; originServerTs = 0; age = null; mTimeZoneRawOffset = getTimeZoneOffset(); stateKey = null; redacts = null; unsentMatrixError = null; unsentException = null; mMatrixId = null; mSentState = SentState.SENT; } /** * @return the sender */ public String getSender() { return (null == sender) ? userId : sender; } /** * Update the sender * * @param aSender the new sender */ public void setSender(String aSender) { sender = userId = aSender; } /** * Update the matrix Id. * * @param aMatrixId the new matrix id. */ public void setMatrixId(String aMatrixId) { mMatrixId = aMatrixId; } /** * @return the matrix id. */ public String getMatrixId() { return mMatrixId; } static final long MAX_ORIGIN_SERVER_TS = 1L << 50L; /** * @return true if originServerTs is valid. */ public boolean isValidOriginServerTs() { return originServerTs < MAX_ORIGIN_SERVER_TS; } /** * @return the originServerTs. */ public long getOriginServerTs() { return originServerTs; } /** * Update the event content. * * @param newContent the new content. */ public void updateContent(JsonElement newContent) { content = newContent; contentAsString = null; } /** * @return true if content has some entries */ public boolean hasContentFields() { boolean res = false; JsonObject json = getContentAsJsonObject(); if (null != json) { Set<Map.Entry<String, JsonElement>> entries = getContentAsJsonObject().entrySet(); res = (null != entries) && (0 != entries.size()); } return res; } /** * @return true if this event was redacted */ public boolean isRedacted() { return (null != unsigned) && (null != unsigned.redacted_because); } static DateFormat mDateFormat = null; static long mFormatterRawOffset = 1234; /** * @return a formatted timestamp. */ public String formattedOriginServerTs() { // avoid displaying weird origin ts if (!isValidOriginServerTs()) { return " "; } else { // the formatter must be updated if the timezone has been updated // else the formatted string are wrong (does not use the current timezone) if ((null == mDateFormat) || (mFormatterRawOffset != getTimeZoneOffset())) { mDateFormat = new SimpleDateFormat("MMM d HH:mm", Locale.getDefault()); mFormatterRawOffset = getTimeZoneOffset(); } return mDateFormat.format(new Date(getOriginServerTs())); } } /** * Update the originServerTs. * * @param anOriginServer the new originServerTs. */ public void setOriginServerTs(long anOriginServer) { originServerTs = anOriginServer; } /** * @return the event type */ public String getType() { if (null != mClearEvent) { return mClearEvent.type; } else { return type; } } /** * Update the event type * * @param aType the new type */ public void setType(String aType) { // TODO manage encryption type = aType; } /** * @return the wire event type */ public String getWireType() { return type; } /** * @return the event content */ public JsonElement getContent() { if (null != mClearEvent) { return mClearEvent.getWireContent(); } else { return getWireContent(); } } /** * @return the wired event content */ public JsonElement getWireContent() { finalizeDeserialization(); return content; } /** * @return the content casted as JsonObject. */ public JsonObject getContentAsJsonObject() { JsonElement cont = getContent(); if ((null != cont) && cont.isJsonObject()) { return cont.getAsJsonObject(); } return null; } /** * @return the prev_content casted as JsonObject. */ public JsonObject getPrevContentAsJsonObject() { finalizeDeserialization(); if ((null != unsigned) && (null != unsigned.prev_content)) { // avoid getting two value for the same thing if (null == prev_content) { prev_content = unsigned.prev_content; } unsigned.prev_content = null; } if ((null != prev_content) && prev_content.isJsonObject()) { return prev_content.getAsJsonObject(); } return null; } /** * @return the content formatted as EventContent. */ public EventContent getEventContent() { if (null != getContent()) { return JsonUtils.toEventContent(getContent()); } return null; } /** * @return the content formatted as EventContent. */ public EventContent getWireEventContent() { if (null != getWireContent()) { return JsonUtils.toEventContent(getWireContent()); } return null; } /** * @return the content formatted as EventContent. */ public EventContent getPrevContent() { if (null != getPrevContentAsJsonObject()) { return JsonUtils.toEventContent(getPrevContentAsJsonObject()); } return null; } /** * @return the event age. */ public long getAge() { if (null != age) { return age; } else if ((null != unsigned) && (null != unsigned.age)) { age = unsigned.age; return age; } return Long.MAX_VALUE; } /** * @return the redacted event id. */ public String getRedacts() { if (null != redacts) { return redacts; } else if (isRedacted()) { redacts = unsigned.redacted_because.redacts; return redacts; } return null; } /** * Create an event from a message. * * @param message the event content * @param anUserId the event user Id * @param aRoomId the vent room Id */ public Event(Message message, String anUserId, String aRoomId) { type = Event.EVENT_TYPE_MESSAGE; content = JsonUtils.toJson(message); originServerTs = System.currentTimeMillis(); sender = userId = anUserId; roomId = aRoomId; mSentState = Event.SentState.SENDING; createDummyEventId(); } /** * Create an event from a content and a type. * * @param aType the event type * @param aContent the event content * @param anUserId the event user Id * @param aRoomId the vent room Id */ public Event(String aType, JsonObject aContent, String anUserId, String aRoomId) { type = aType; content = aContent; originServerTs = System.currentTimeMillis(); sender = userId = anUserId; roomId = aRoomId; mSentState = Event.SentState.SENDING; createDummyEventId(); } /** * Some events are not sent by the server. * They are temporary stored until to get the server response. */ public void createDummyEventId() { eventId = roomId + "-" + originServerTs; age = DUMMY_EVENT_AGE; } /** * @return true if the event is a dummy id i.e this event has been created with createDummyEventId. */ public boolean isDummyEvent() { return (roomId + "-" + originServerTs).equals(eventId); } /** * Update the pagination token. * * @param token the new token. */ public void setInternalPaginationToken(String token) { mToken = token; mIsInternalPaginationToken = true; } /** * @return true if the token has been set by setInternalPaginationToken. */ public boolean isInternalPaginationToken() { return mIsInternalPaginationToken; } /** * @return true if the event has a token. */ public boolean hasToken() { return (null != mToken) && !mIsInternalPaginationToken; } /** * @return true if the event if a call event. */ public boolean isCallEvent() { return EVENT_TYPE_CALL_INVITE.equals(getType()) || EVENT_TYPE_CALL_CANDIDATES.equals(getType()) || EVENT_TYPE_CALL_ANSWER.equals(getType()) || EVENT_TYPE_CALL_HANGUP.equals(getType()); } /** * Make a deep copy of this room state object. * * @return the copy */ public Event deepCopy() { finalizeDeserialization(); Event copy = new Event(); copy.type = type; copy.content = content; copy.contentAsString = contentAsString; copy.eventId = eventId; copy.roomId = roomId; copy.userId = userId; copy.sender = sender; copy.originServerTs = originServerTs; copy.mTimeZoneRawOffset = mTimeZoneRawOffset; copy.age = age; copy.stateKey = stateKey; copy.prev_content = prev_content; copy.prev_content_as_string = prev_content_as_string; copy.unsigned = unsigned; copy.invite_room_state = invite_room_state; copy.redacts = redacts; copy.mSentState = mSentState; copy.unsentException = unsentException; copy.unsentMatrixError = unsentMatrixError; copy.mMatrixId = mMatrixId; copy.mToken = mToken; copy.mIsInternalPaginationToken = mIsInternalPaginationToken; return copy; } /** * Check if the current event can resent. * * @return true if it can be resent. */ public boolean canBeResent() { return (mSentState == SentState.WAITING_RETRY) || (mSentState == SentState.UNDELIVERABLE) || (mSentState == SentState.FAILED_UNKNOWN_DEVICES); } /** * Check if the current event is encrypting. * * @return true if the message encryption is in progress. */ public boolean isEncrypting() { return (mSentState == SentState.ENCRYPTING); } /** * Check if the current event is sending. * * @return true if it is sending. */ public boolean isSending() { return (mSentState == SentState.SENDING) || (mSentState == SentState.WAITING_RETRY); } /** * Tell if the message is undeliverable * * @return true if the event is undeliverable */ public boolean isUndeliverable() { return (mSentState == SentState.UNDELIVERABLE); } /** * Tells if the message sending failed because some unknown devices have benn detected. * * @return true if some unknown devices have benn detected. */ public boolean isUnkownDevice() { return (mSentState == SentState.FAILED_UNKNOWN_DEVICES); } /** * Check if the current event is sent. * * @return true if it is sent. */ public boolean isSent() { return (mSentState == SentState.SENT); } /** * @return the media URLs defined in the event. */ public List<String> getMediaUrls() { ArrayList<String> urls = new ArrayList<>(); if (Event.EVENT_TYPE_MESSAGE.equals(getType())) { String msgType = JsonUtils.getMessageMsgType(getContent()); if (Message.MSGTYPE_IMAGE.equals(msgType)) { ImageMessage imageMessage = JsonUtils.toImageMessage(getContent()); if (null != imageMessage.getUrl()) { urls.add(imageMessage.getUrl()); } if (null != imageMessage.getThumbnailUrl()) { urls.add(imageMessage.getThumbnailUrl()); } } else if (Message.MSGTYPE_FILE.equals(msgType) || Message.MSGTYPE_AUDIO.equals(msgType) ) { FileMessage fileMessage = JsonUtils.toFileMessage(getContent()); if (null != fileMessage.getUrl()) { urls.add(fileMessage.getUrl()); } } else if (Message.MSGTYPE_VIDEO.equals(msgType)) { VideoMessage videoMessage = JsonUtils.toVideoMessage(getContent()); if (null != videoMessage.getUrl()) { urls.add(videoMessage.getUrl()); } } } return urls; } /** * Tells if the current event is uploading a media. * * @param mediasCache the media cache * @return true if the event is uploading a media. */ public boolean isUploadingMedias(MXMediasCache mediasCache) { List<String> urls = getMediaUrls(); for (String url : urls) { if (mediasCache.getProgressValueForUploadId(url) >= 0) { return true; } } return false; } /** * Tells if the current event is downloading a media. * * @param mediasCache the media cache * @return true if the event is downloading a media. */ public boolean isDownloadingMedias(MXMediasCache mediasCache) { List<String> urls = getMediaUrls(); for (String url : urls) { if (mediasCache.getProgressValueForDownloadId(mediasCache.downloadIdFromUrl(url)) >= 0) { return true; } } return false; } @Override public java.lang.String toString() { // build the string by hand String text = "{\n"; text += " \"age\" : " + age + ",\n"; text += " \"content\" {\n"; if (null != getWireContent()) { if (getWireContent().isJsonArray()) { for (JsonElement e : getWireContent().getAsJsonArray()) { text += " " + e.toString() + "\n,"; } } else if (getWireContent().isJsonObject()) { for (Map.Entry<String, JsonElement> e : getWireContent().getAsJsonObject().entrySet()) { text += " \"" + e.getKey() + ": " + e.getValue().toString() + ",\n"; } } else { text += getWireContent().toString(); } } text += " },\n"; text += " \"eventId\": \"" + eventId + "\",\n"; text += " \"originServerTs\": " + originServerTs + ",\n"; text += " \"roomId\": \"" + roomId + "\",\n"; text += " \"type\": \"" + type + "\",\n"; text += " \"userId\": \"" + userId + "\"\n"; text += " \"sender\": \"" + sender + "\"\n"; text += " \"\n\n Sent state : "; if (mSentState == SentState.UNSENT) { text += "UNSENT"; } else if (mSentState == SentState.SENDING) { text += "SENDING"; } else if (mSentState == SentState.WAITING_RETRY) { text += "WAITING_RETRY"; } else if (mSentState == SentState.SENT) { text += "SENT"; } else if (mSentState == SentState.UNDELIVERABLE) { text += "UNDELIVERABLE"; } else if (mSentState == SentState.FAILED_UNKNOWN_DEVICES) { text += "FAILED UNKNOWN DEVICES"; } text += "\n\n"; if (null != unsentException) { text += "\n\n Exception reason: " + unsentException.getMessage() + "\n"; } if (null != unsentMatrixError) { text += "\n\n Matrix reason: " + unsentMatrixError.getLocalizedMessage() + "\n"; } text += "}"; return text; } @Override public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { if (input.readBoolean()) { type = input.readUTF(); } if (input.readBoolean()) { contentAsString = input.readUTF(); } if (input.readBoolean()) { prev_content_as_string = input.readUTF(); } if (input.readBoolean()) { eventId = input.readUTF(); } if (input.readBoolean()) { roomId = input.readUTF(); } if (input.readBoolean()) { userId = input.readUTF(); } if (input.readBoolean()) { sender = input.readUTF(); } originServerTs = input.readLong(); if (input.readBoolean()) { age = input.readLong(); } if (input.readBoolean()) { stateKey = input.readUTF(); } if (input.readBoolean()) { unsigned = (UnsignedData)input.readObject(); } if (input.readBoolean()) { redacts = input.readUTF(); } if (input.readBoolean()) { invite_room_state = (List<Event>) input.readObject(); } if (input.readBoolean()) { unsentException = (Exception) input.readObject(); } if (input.readBoolean()) { unsentMatrixError = (MatrixError)input.readObject(); } mSentState = (SentState) input.readObject(); if (input.readBoolean()) { mToken = input.readUTF(); } mIsInternalPaginationToken = input.readBoolean(); if (input.readBoolean()) { mMatrixId = input.readUTF(); } mTimeZoneRawOffset = input.readLong(); } @Override public void writeExternal(ObjectOutput output) throws IOException { prepareSerialization(); output.writeBoolean(null != type); if (null != type) { output.writeUTF(type); } output.writeBoolean(null != contentAsString); if (null != contentAsString) { output.writeUTF(contentAsString); } output.writeBoolean(null != prev_content_as_string); if (null != prev_content_as_string) { output.writeUTF(prev_content_as_string); } output.writeBoolean(null != eventId); if (null != eventId) { output.writeUTF(eventId); } output.writeBoolean(null != roomId); if (null != roomId) { output.writeUTF(roomId); } output.writeBoolean(null != userId); if (null != userId) { output.writeUTF(userId); } output.writeBoolean(null != sender); if (null != sender) { output.writeUTF(sender); } output.writeLong(originServerTs); output.writeBoolean(null != age); if (null != age) { output.writeLong(age); } output.writeBoolean(null != stateKey); if (null != stateKey) { output.writeUTF(stateKey); } output.writeBoolean(null != unsigned); if (null != unsigned) { output.writeObject(unsigned); } output.writeBoolean(null != redacts); if (null != redacts) { output.writeUTF(redacts); } output.writeBoolean(null != invite_room_state); if (null != invite_room_state) { output.writeObject(invite_room_state); } output.writeBoolean(null != unsentException); if (null != unsentException) { output.writeObject(unsentException); } output.writeBoolean(null != unsentMatrixError); if (null != unsentMatrixError) { output.writeObject(unsentMatrixError); } output.writeObject(mSentState); output.writeBoolean(null != mToken); if (null != mToken) { output.writeUTF(mToken); } output.writeBoolean(mIsInternalPaginationToken); output.writeBoolean(null != mMatrixId); if (null != mMatrixId) { output.writeUTF(mMatrixId); } output.writeLong(mTimeZoneRawOffset); } /** * Init some internal fields to serialize the event. */ private void prepareSerialization() { if ((null != content) && (null == contentAsString)) { contentAsString = content.toString(); } if ((null != getPrevContentAsJsonObject()) && (null == prev_content_as_string)) { prev_content_as_string = getPrevContentAsJsonObject().toString(); } if ((null != unsigned) && (null != unsigned.prev_content)) { unsigned.prev_content = null; } } /** * Deserialize the event. */ private void finalizeDeserialization() { if ((null != contentAsString) && (null == content)) { try { content = new JsonParser().parse(contentAsString).getAsJsonObject(); } catch (Exception e) { Log.e(LOG_TAG, "finalizeDeserialization : contentAsString deserialization " + e.getMessage()); contentAsString = null; } } if ((null != prev_content_as_string) && (null == prev_content)) { try { prev_content = new JsonParser().parse(prev_content_as_string).getAsJsonObject(); } catch (Exception e) { Log.e(LOG_TAG, "finalizeDeserialization : prev_content_as_string deserialization " + e.getMessage()); prev_content_as_string = null; } } } /** * Filter a JsonObject to keep only the allowed keys. * * @param aContent the JsonObject to filter. * @param allowedKeys the allowed keys list. * @return the filtered JsonObject */ private static JsonObject filterInContentWithKeys(JsonObject aContent, ArrayList<String> allowedKeys) { // sanity check if (null == aContent) { return null; } JsonObject filteredContent = new JsonObject(); // remove any key if ((null == allowedKeys) || (0 == allowedKeys.size())) { return new JsonObject(); } Set<Map.Entry<String, JsonElement>> entries = aContent.entrySet(); if (null != entries) { for (Map.Entry<String, JsonElement> entry : entries) { if (allowedKeys.indexOf(entry.getKey()) >= 0) { filteredContent.add(entry.getKey(), entry.getValue()); } } } return filteredContent; } /** * Prune the event which removes all keys we don't know about or think could potentially be dodgy. * This is used when we "redact" an event. We want to remove all fields that the user has specified, * but we do want to keep necessary information like type, state_key etc. * * @param redactionEvent the event which triggers this redaction */ public void prune(Event redactionEvent) { // Filter in event by keeping only the following keys ArrayList<String> allowedKeys; // Add filtered content, allowed keys in content depends on the event type if (TextUtils.equals(Event.EVENT_TYPE_STATE_ROOM_MEMBER, type)) { allowedKeys = new ArrayList<>(Arrays.asList("membership")); } else if (TextUtils.equals(Event.EVENT_TYPE_STATE_ROOM_CREATE, type)) { allowedKeys = new ArrayList<>(Arrays.asList("creator")); } else if (TextUtils.equals(Event.EVENT_TYPE_STATE_ROOM_JOIN_RULES, type)) { allowedKeys = new ArrayList<>(Arrays.asList("join_rule")); } else if (TextUtils.equals(Event.EVENT_TYPE_STATE_ROOM_POWER_LEVELS, type)) { allowedKeys = new ArrayList<>(Arrays.asList("users", "users_default", "events", "events_default", "state_default", "ban", "kick", "redact", "invite")); } else if (TextUtils.equals(Event.EVENT_TYPE_STATE_ROOM_ALIASES, type)) { allowedKeys = new ArrayList<>(Arrays.asList("aliases")); } else if (TextUtils.equals(Event.EVENT_TYPE_STATE_CANONICAL_ALIAS, type)) { allowedKeys = new ArrayList<>(Arrays.asList("alias")); } else if (TextUtils.equals(Event.EVENT_TYPE_FEEDBACK, type)) { allowedKeys = new ArrayList<>(Arrays.asList("type", "target_event_id")); } else if (TextUtils.equals(Event.EVENT_TYPE_MESSAGE_ENCRYPTED, type)) { mClearEvent = null; mKeysProved = null; mKeysClaimed = null; allowedKeys = null; } else { allowedKeys = null; } this.content = filterInContentWithKeys(getContentAsJsonObject(), allowedKeys); this.prev_content = filterInContentWithKeys(getPrevContentAsJsonObject(), allowedKeys); this.prev_content_as_string = null; this.contentAsString = null; if (null != redactionEvent) { if (null == unsigned) { unsigned = new UnsignedData(); } unsigned.redacted_because = new RedactedBecause(); unsigned.redacted_because.type = redactionEvent.type; unsigned.redacted_because.origin_server_ts = redactionEvent.originServerTs; unsigned.redacted_because.sender = redactionEvent.sender; unsigned.redacted_because.event_id = redactionEvent.eventId; unsigned.redacted_because.unsigned = redactionEvent.unsigned; unsigned.redacted_because.redacts = redactionEvent.redacts; unsigned.redacted_because.content = new RedactedContent(); JsonObject contentAsJson = getContentAsJsonObject(); if ((null != contentAsJson) && contentAsJson.has("reason")) { try { unsigned.redacted_because.content.reason = contentAsJson.get("reason").getAsString(); } catch (Exception e) { Log.e(LOG_TAG, "unsigned.redacted_because.content.reason failed " + e.getLocalizedMessage()); } } } } //============================================================================================================== // Crypto //============================================================================================================== /** * For encrypted events, the plaintext payload for the event. * This is a small MXEvent instance with typically value for `type` and 'content' fields. */ private transient Event mClearEvent; /** * The keys that must have been owned by the sender of this encrypted event. * * @discussion These don't necessarily have to come from this event itself, but may be * implied by the cryptographic session. */ private transient Map<String, String> mKeysProved; /** * The additional keys the sender of this encrypted event claims to possess. * * @discussion These don't necessarily have to come from this event itself, but may be * implied by the cryptographic session. * For example megolm messages don't claim keys directly, but instead * inherit a claim from the olm message that established the session. * The keys that must have been owned by the sender of this encrypted event. */ private transient Map<String, String> mKeysClaimed; // linked crypto error private MXCryptoError mCryptoError; /** * True if this event is encrypted. */ public boolean isEncrypted() { return TextUtils.equals(getWireType(), EVENT_TYPE_MESSAGE_ENCRYPTED); } /** * The curve25519 key that sent this event. */ public String senderKey() { if (null != getKeysProved()) { return getKeysProved().get("curve25519"); } else { return null; } } /** * @return the keys proved */ public Map<String, String> getKeysProved() { if (null != mClearEvent) { return mClearEvent.mKeysProved; } return mKeysProved; } /** * Update the key proved * * @param keysProved the keys proved */ public void setKeysProved(Map<String, String> keysProved) { if (null != mClearEvent) { mClearEvent.mKeysProved = keysProved; } else { mKeysProved = keysProved; } } /** * @return the keys claimed map */ public Map<String, String> getKeysClaimed() { if (null != mClearEvent) { return mClearEvent.mKeysClaimed; } return mKeysClaimed; } /** * Update tke ley claimed * * @param keysClaimed the new key claimed map */ public void setKeysClaimed(Map<String, String> keysClaimed) { if (null != mClearEvent) { mClearEvent.mKeysClaimed = keysClaimed; } else { mKeysClaimed = keysClaimed; } } /** * @return the linked crypto error */ public MXCryptoError getCryptoError() { return mCryptoError; } /** * Update the linked crypto error * * @param error the new crypto error. */ public void setCryptoError(MXCryptoError error) { mCryptoError = error; } /** * Update the clear event * * @param aClearEvent the clean event. */ public void setClearEvent(Event aClearEvent) { mClearEvent = aClearEvent; } /** * @return the clear event */ public Event getClearEvent() { return mClearEvent; } }