/* * VoIP.ms SMS * Copyright (C) 2015-2016 Michael Kourlas * * 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.kourlas.voipms_sms.model; import android.support.annotation.NonNull; import net.kourlas.voipms_sms.db.Database; import net.kourlas.voipms_sms.utils.Utils; import org.json.JSONException; import org.json.JSONObject; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; /** * Represents a single SMS message. */ public class Message implements Comparable<Message> { /** * The database ID of the message. */ private final Long databaseId; /** * The ID assigned to the message by VoIP.ms. */ private final Long voipId; /** * The type of the message (incoming or outgoing). */ private final Type type; /** * The DID associated with the message. */ private final String did; /** * The contact associated with the message. */ private final String contact; /** * The date of the message. */ private Date date; /** * The text of the message. */ private String text; /** * Whether or not the message is unread. */ private boolean isUnread; /** * Whether or not the message is deleted. */ private boolean isDeleted; /** * Whether or not the message has been delivered. */ private boolean isDelivered; /** * Whether or not the message is currently in the process of being * delivered. */ private boolean isDeliveryInProgress; /** * Whether or not the message is a draft. */ private boolean isDraft; /** * Initializes a new instance of the Message class. This constructor is * intended for use when creating a Message object using information from * the application database. * * @param databaseId The database ID of the message. * @param voipId The ID assigned to the message by VoIP.ms. * @param date The UNIX timestamp of the message. * @param type The type of the message (1 for incoming, * 0 for outgoing). * @param did The DID associated with the message. * @param contact The contact associated with the message. * @param text The text of the message. * @param isUnread Whether or not the message is unread (1 for * true, 0 for false). * @param isDeleted Whether or not the message has been deleted * locally (1 for true, 0 for false). * @param isDelivered Whether or not the message has been * delivered (1 for true, 0 for false). * @param isDeliveryInProgress Whether or not the message is currently in * the process of being delivered (1 for * true, 0 for false). * @param isDraft Whether or not the message is a draft * (1 for true, 0 for false). */ public Message(long databaseId, Long voipId, long date, long type, String did, String contact, String text, long isUnread, long isDeleted, long isDelivered, long isDeliveryInProgress, long isDraft) { this.databaseId = databaseId; this.voipId = voipId; this.date = new Date(date * 1000); if (type != 0 && type != 1) { throw new IllegalArgumentException("type must be 0 or 1."); } this.type = type == 1 ? Type.INCOMING : Type.OUTGOING; if (!Utils.getDigitsOfString(did).equals(did)) { throw new IllegalArgumentException("did must consist only of" + " numbers."); } this.did = did; if (!Utils.getDigitsOfString(contact).equals(contact)) { throw new IllegalArgumentException("contact must consist only of" + " numbers."); } this.contact = contact; this.text = text; if (isUnread != 0 && isUnread != 1) { throw new IllegalArgumentException("isUnread must be 0 or 1."); } this.isUnread = isUnread == 1; if (isDeleted != 0 && isDeleted != 1) { throw new IllegalArgumentException("isDeleted must be 0 or 1."); } this.isDeleted = isDeleted == 1; if (isDelivered != 0 && isDelivered != 1) { throw new IllegalArgumentException("isDelivered must be 0 or 1."); } this.isDelivered = isDelivered == 1; if (isDeliveryInProgress != 0 && isDeliveryInProgress != 1) { throw new IllegalArgumentException("isDeliveryInProgress must be" + " 0 or 1."); } this.isDeliveryInProgress = isDeliveryInProgress == 1; if (isDraft != 0 && isDraft != 1) { throw new IllegalArgumentException("isDraft must be 0 or 1."); } this.isDraft = isDraft == 1; // Remove trailing newline found in messages retrieved from VoIP.ms if (this.type == Type.INCOMING && text.endsWith("\n")) { this.text = text.substring(0, text.length() - 1); } } /** * Initializes a new instance of the Message class. This constructor is * intended for use when creating a Message object using information from * the VoIP.ms API. * * @param voipId The ID assigned to the message by VoIP.ms. * @param date The UNIX timestamp of the message. * @param type The type of the message (1 for incoming, 0 for outgoing). * @param did The DID associated with the message. * @param contact The contact associated with the message. * @param text The text of the message. */ public Message(String voipId, String date, String type, String did, String contact, String text) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); sdf.setTimeZone(TimeZone.getTimeZone("America/New_York")); this.databaseId = null; this.voipId = Long.parseLong(voipId); this.date = sdf.parse(date); if (!type.equals("0") && !type.equals("1")) { throw new IllegalArgumentException("type must be 0 or 1."); } this.type = type.equals("1") ? Type.INCOMING : Type.OUTGOING; if (!Utils.getDigitsOfString(did).equals(did)) { throw new IllegalArgumentException("did must consist only of" + " numbers."); } this.did = did; if (!Utils.getDigitsOfString(contact).equals(contact)) { throw new IllegalArgumentException("contact must consist only of" + " numbers."); } this.contact = contact; this.text = text; this.isUnread = type.equals("1"); this.isDeleted = false; this.isDelivered = true; this.isDeliveryInProgress = false; this.isDraft = false; } /** * Initializes a new instance of the Message class. This constructor is * intended for use when creating a new Message object that will be sent to * another contact using the VoIP.ms API. * * @param did The DID associated with the message. * @param contact The contact associated with the message. * @param text The text of the message. */ public Message(String did, String contact, String text) { this.databaseId = null; this.voipId = null; this.date = new Date(); this.type = Type.OUTGOING; if (!Utils.getDigitsOfString(did).equals(did)) { throw new IllegalArgumentException("did must consist only of" + " numbers."); } this.did = did; if (!Utils.getDigitsOfString(contact).equals(contact)) { throw new IllegalArgumentException("contact must consist only of" + " numbers."); } this.contact = contact; this.text = text; this.isUnread = false; this.isDeleted = false; this.isDelivered = true; this.isDeliveryInProgress = true; this.isDraft = false; } /** * Gets the date of the message. * * @return The date of the message. */ public Date getDate() { return date; } /** * Sets the date of the message. * * @param date The date of the message. */ public void setDate(Date date) { this.date = date; } /** * Gets the type of the message (incoming or outgoing). * * @return The type of the message (incoming or outgoing). */ public Type getType() { return type; } /** * Gets whether or not the message is deleted. * * @return Whether or not the message is deleted. */ public boolean isDeleted() { return isDeleted; } /** * Gets whether or not the message has been delivered. * * @return Whether or not the message has been delivered. */ public boolean isDelivered() { return isDelivered; } /** * Gets whether or not the message is being delivered. * * @return Whether or not the message is being delivered. */ public boolean isDeliveryInProgress() { return isDeliveryInProgress; } /** * Gets whether or not the message is a draft. * * @return Whether or not the message is a draft. */ public boolean isDraft() { return isDraft; } /** * Sets whether or not the message is a draft. * * @param isDraft Whether or not the message is a draft. */ public void setDraft(boolean isDraft) { this.isDraft = isDraft; } /** * Returns whether or not the message is identical to another object. * * @param o The other object. * @return Whether or not the message is identical to another object. */ @Override public boolean equals(Object o) { if (o instanceof Message) { if (this == o) { return true; } Message other = (Message) o; boolean databaseIdEquals = (databaseId == null && other.databaseId == null) || (databaseId != null && other.databaseId != null && databaseId .equals(other.databaseId)); boolean voipIdEquals = (voipId == null && other.voipId == null) || (voipId != null && other.voipId != null && voipId.equals(other.voipId)); return databaseIdEquals && voipIdEquals && date.equals(other.date) && type == other.type && did.equals(other.did) && contact.equals(other.contact) && text.equals(other.text) && isUnread() == other.isUnread() && isDeleted == other.isDeleted && isDelivered == other.isDelivered && isDeliveryInProgress == other.isDeliveryInProgress && isDraft == other.isDraft; } else { return super.equals(o); } } /** * Gets whether or not the message is unread. * * @return Whether or not the message is unread. */ public boolean isUnread() { // Outgoing messages must be read return isUnread && type == Type.INCOMING; } /** * Sets whether or not the message is unread. * * @param unread Whether or not the message is unread. */ public void setUnread(boolean unread) { isUnread = unread; } /** * Returns true if this message and the specified message are part of the * same conversation. * * @param another The other message. * @return True if this message and the specified message are part of the * same conversation. */ public boolean equalsConversation(Message another) { return this.contact.equals(another.contact) && this.did.equals(another.did); } /** * Returns true if this message and the specified message have the same * database ID. * * @param another The other message. * @return True if this message and the specified message have the same * database ID. */ public boolean equalsDatabaseId(Message another) { return this.getDatabaseId() != null && another.getDatabaseId() != null && this.getDatabaseId() .equals(another.getDatabaseId()); } /** * Gets the database ID of the message. This value may be null if no ID has * been yet been assigned to the message (i.e. if the message has not yet * been inserted into the database). * * @return The database ID of the message. */ public Long getDatabaseId() { return databaseId; } /** * Compares this message to another message. Compares according to ideal * sorting order for the conversations view. * * @param another The other message. * @return -1, 1, or 0 if this message is less than, greater than, or * equal to the other message. */ @Override public int compareTo(@NonNull Message another) { if (this.isDraft && !another.isDraft) { return -1; } else if (!this.isDraft && another.isDraft) { return 1; } if (this.date.getTime() > another.date.getTime()) { return -1; } else if (this.date.getTime() < another.date.getTime()) { return 1; } if (this.databaseId != null && another.databaseId != null) { if (this.databaseId > another.databaseId) { return -1; } else if (this.databaseId < another.databaseId) { return 1; } } return 0; } /** * Returns a JSON version of this message. * * @return A JSON version of this message. */ public JSONObject toJSON() { try { JSONObject jsonObject = new JSONObject(); if (databaseId != null) { jsonObject.put(Database.COLUMN_DATABASE_ID, getDatabaseId()); } if (voipId != null) { jsonObject.put(Database.COLUMN_VOIP_ID, getVoipId()); } jsonObject.put(Database.COLUMN_DATE, getDateInDatabaseFormat()); jsonObject.put(Database.COLUMN_TYPE, getTypeInDatabaseFormat()); jsonObject.put(Database.COLUMN_DID, getDid()); jsonObject.put(Database.COLUMN_CONTACT, getContact()); jsonObject.put(Database.COLUMN_MESSAGE, getText()); jsonObject.put(Database.COLUMN_UNREAD, isUnreadInDatabaseFormat()); jsonObject .put(Database.COLUMN_DELETED, isDeletedInDatabaseFormat()); jsonObject .put(Database.COLUMN_DELIVERED, isDeliveredInDatabaseFormat()); jsonObject.put(Database.COLUMN_DELIVERY_IN_PROGRESS, isDeliveryInProgressInDatabaseFormat()); jsonObject.put(Database.COLUMN_DRAFT, isDraftInDatabaseFormat()); return jsonObject; } catch (JSONException ex) { // This should never happen throw new Error(); } } /** * Gets the ID assigned to the message by VoIP.ms. This value may be null * if no ID has yet been assigned to the message (i.e. if the message has * not yet been sent). * * @return The ID assigned to the message by VoIP.ms. */ public Long getVoipId() { return voipId; } /** * Gets the date of the message in database format. * * @return The date of the message in database format. */ public long getDateInDatabaseFormat() { return date.getTime() / 1000; } /** * Gets the type of the message in database format. * * @return The type of the message in database format. */ public long getTypeInDatabaseFormat() { return type == Type.INCOMING ? 1 : 0; } /** * Gets the DID associated with the message. * * @return The DID associated with the message. */ public String getDid() { return did; } /** * Gets the contact associated with the message. * * @return The contact associated with the message. */ public String getContact() { return contact; } /** * Gets the text of the message. * * @return The text of the message. */ public String getText() { return text; } /** * Sets the text of the message. * * @param text The text of the message. */ public void setText(String text) { this.text = text; } /** * Gets whether or not the message is unread in database format. * * @return Whether or not the message is unread in database format. */ public int isUnreadInDatabaseFormat() { // Outgoing messages must be read return (isUnread && type == Type.INCOMING) ? 1 : 0; } /** * Gets whether or not the message is deleted in database format. * * @return Whether or not the message is deleted in database format. */ public int isDeletedInDatabaseFormat() { return isDeleted ? 1 : 0; } /** * Gets whether or not the message has been delivered in database format. * * @return Whether or not the message has been delivered in databse format. */ public int isDeliveredInDatabaseFormat() { return isDelivered ? 1 : 0; } /** * Gets whether or not the message is being delivered in database format. * * @return Whether or not the message is being delivered in database format. */ public int isDeliveryInProgressInDatabaseFormat() { return isDeliveryInProgress ? 1 : 0; } /** * Gets whether or not the message is a draft in database format. * * @return Whether or not the message is a draft in database format. */ public int isDraftInDatabaseFormat() { return isDraft ? 1 : 0; } /** * Represents the type of the message. */ public enum Type { /** * Represents an outgoing message (a message coming from the DID). */ OUTGOING, /** * Represents an incoming message (a message addressed to the DID). */ INCOMING, } }