1 /* 2 * Copyright (C) 2015 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.android.bluetooth.map; 16 17 import android.util.Log; 18 19 import com.android.bluetooth.SignedLongLong; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 import org.xmlpull.v1.XmlSerializer; 24 25 import java.io.IOException; 26 import java.text.ParseException; 27 import java.text.SimpleDateFormat; 28 import java.util.Date; 29 import java.util.Locale; 30 import java.util.Objects; 31 32 // Next tag value for ContentProfileErrorReportUtils.report(): 1 33 public class BluetoothMapConvoContactElement 34 implements Comparable<BluetoothMapConvoContactElement> { 35 private static final String TAG = BluetoothMapConvoContactElement.class.getSimpleName(); 36 37 public static final long CONTACT_ID_TYPE_SMS_MMS = 1; 38 public static final long CONTACT_ID_TYPE_EMAIL = 2; 39 public static final long CONTACT_ID_TYPE_IM = 3; 40 41 private static final String XML_ATT_PRIORITY = "priority"; 42 private static final String XML_ATT_PRESENCE_STATUS = "presence_status"; 43 private static final String XML_ATT_PRESENCE_AVAILABILITY = "presence_availability"; 44 private static final String XML_ATT_X_BT_UID = "x_bt_uid"; 45 private static final String XML_ATT_LAST_ACTIVITY = "last_activity"; 46 private static final String XML_ATT_CHAT_STATE = "chat_state"; 47 private static final String XML_ATT_NAME = "name"; 48 private static final String XML_ATT_DISPLAY_NAME = "display_name"; 49 private static final String XML_ATT_UCI = "x_bt_uci"; 50 protected static final String XML_TAG_CONVOCONTACT = "convocontact"; 51 52 private String mUci = null; 53 private String mName = null; 54 private String mDisplayName = null; 55 private String mPresenceStatus = null; 56 private int mPresenceAvailability = -1; 57 private int mPriority = -1; 58 private long mLastActivity = -1; 59 private SignedLongLong mBtUid = null; 60 private int mChatState = -1; 61 createFromMapContact( MapContact contact, String address)62 public static BluetoothMapConvoContactElement createFromMapContact( 63 MapContact contact, String address) { 64 BluetoothMapConvoContactElement newElement = new BluetoothMapConvoContactElement(); 65 newElement.mUci = address; 66 // TODO: For now we use the ID as BT-UID 67 newElement.mBtUid = new SignedLongLong(contact.id(), 0); 68 newElement.mDisplayName = contact.name(); 69 return newElement; 70 } 71 BluetoothMapConvoContactElement( String uci, String name, String displayName, String presenceStatus, int presenceAvailability, long lastActivity, int chatState, int priority, String btUid)72 public BluetoothMapConvoContactElement( 73 String uci, 74 String name, 75 String displayName, 76 String presenceStatus, 77 int presenceAvailability, 78 long lastActivity, 79 int chatState, 80 int priority, 81 String btUid) { 82 this.mUci = uci; 83 this.mName = name; 84 this.mDisplayName = displayName; 85 this.mPresenceStatus = presenceStatus; 86 this.mPresenceAvailability = presenceAvailability; 87 this.mLastActivity = lastActivity; 88 this.mChatState = chatState; 89 this.mPresenceStatus = presenceStatus; 90 this.mPriority = priority; 91 if (btUid != null) { 92 this.mBtUid = SignedLongLong.fromString(btUid); 93 } 94 } 95 BluetoothMapConvoContactElement()96 public BluetoothMapConvoContactElement() { 97 // TODO Auto-generated constructor stub 98 } 99 getPresenceStatus()100 public String getPresenceStatus() { 101 return mPresenceStatus; 102 } 103 getDisplayName()104 public String getDisplayName() { 105 return mDisplayName; 106 } 107 setDisplayName(String displayName)108 public void setDisplayName(String displayName) { 109 this.mDisplayName = displayName; 110 } 111 setPresenceStatus(String presenceStatus)112 public void setPresenceStatus(String presenceStatus) { 113 this.mPresenceStatus = presenceStatus; 114 } 115 getPresenceAvailability()116 public int getPresenceAvailability() { 117 return mPresenceAvailability; 118 } 119 setPresenceAvailability(int presenceAvailability)120 public void setPresenceAvailability(int presenceAvailability) { 121 this.mPresenceAvailability = presenceAvailability; 122 } 123 getPriority()124 public int getPriority() { 125 return mPriority; 126 } 127 setPriority(int priority)128 public void setPriority(int priority) { 129 this.mPriority = priority; 130 } 131 getName()132 public String getName() { 133 return mName; 134 } 135 setName(String name)136 public void setName(String name) { 137 this.mName = name; 138 } 139 getBtUid()140 public String getBtUid() { 141 return mBtUid.toHexString(); 142 } 143 setBtUid(SignedLongLong btUid)144 public void setBtUid(SignedLongLong btUid) { 145 this.mBtUid = btUid; 146 } 147 getChatState()148 public int getChatState() { 149 return mChatState; 150 } 151 setChatState(int chatState)152 public void setChatState(int chatState) { 153 this.mChatState = chatState; 154 } 155 setChatState(String chatState)156 public void setChatState(String chatState) { 157 this.mChatState = Integer.valueOf(chatState); 158 } 159 160 @SuppressWarnings("JavaUtilDate") // TODO: b/365629730 -- prefer Instant or LocalDate getLastActivityString()161 public String getLastActivityString() { 162 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ROOT); 163 Date date = new Date(mLastActivity); 164 return format.format(date); // Format to YYYYMMDDTHHMMSS local time 165 } 166 setLastActivity(long dateTime)167 public void setLastActivity(long dateTime) { 168 this.mLastActivity = dateTime; 169 } 170 171 @SuppressWarnings("JavaUtilDate") // TODO: b/365629730 -- prefer Instant or LocalDate setLastActivity(String lastActivity)172 public void setLastActivity(String lastActivity) throws ParseException { 173 SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ROOT); 174 Date date = format.parse(lastActivity); 175 this.mLastActivity = date.getTime(); 176 } 177 setContactId(String uci)178 public void setContactId(String uci) { 179 this.mUci = uci; 180 } 181 getContactId()182 public String getContactId() { 183 return mUci; 184 } 185 186 @Override compareTo(BluetoothMapConvoContactElement e)187 public int compareTo(BluetoothMapConvoContactElement e) { 188 if (this.mLastActivity < e.mLastActivity) { 189 return 1; 190 } else if (this.mLastActivity > e.mLastActivity) { 191 return -1; 192 } else { 193 return 0; 194 } 195 } 196 197 /* Encode the MapConvoContactElement into the StringBuilder reference. 198 * Here we have taken the choice not to report empty attributes, to reduce the 199 * amount of data to be transferred over BT. */ encode(XmlSerializer xmlConvoElement)200 public void encode(XmlSerializer xmlConvoElement) 201 throws IllegalArgumentException, IllegalStateException, IOException { 202 // construct the XML tag for a single contact in the convolisting element. 203 xmlConvoElement.startTag(null, XML_TAG_CONVOCONTACT); 204 if (mUci != null) { 205 xmlConvoElement.attribute(null, XML_ATT_UCI, mUci); 206 } 207 if (mDisplayName != null) { 208 xmlConvoElement.attribute( 209 null, XML_ATT_DISPLAY_NAME, BluetoothMapUtils.stripInvalidChars(mDisplayName)); 210 } 211 if (mName != null) { 212 xmlConvoElement.attribute( 213 null, XML_ATT_NAME, BluetoothMapUtils.stripInvalidChars(mName)); 214 } 215 if (mChatState != -1) { 216 xmlConvoElement.attribute(null, XML_ATT_CHAT_STATE, String.valueOf(mChatState)); 217 } 218 if (mLastActivity != -1) { 219 xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY, this.getLastActivityString()); 220 } 221 if (mBtUid != null) { 222 xmlConvoElement.attribute(null, XML_ATT_X_BT_UID, mBtUid.toHexString()); 223 } 224 if (mPresenceAvailability != -1) { 225 xmlConvoElement.attribute( 226 null, XML_ATT_PRESENCE_AVAILABILITY, String.valueOf(mPresenceAvailability)); 227 } 228 if (mPresenceStatus != null) { 229 xmlConvoElement.attribute(null, XML_ATT_PRESENCE_STATUS, mPresenceStatus); 230 } 231 if (mPriority != -1) { 232 xmlConvoElement.attribute(null, XML_ATT_PRIORITY, String.valueOf(mPriority)); 233 } 234 235 xmlConvoElement.endTag(null, XML_TAG_CONVOCONTACT); 236 } 237 238 /** 239 * Call this function to create a BluetoothMapConvoContactElement. Will consume the end-tag. 240 * 241 * @param parser must point into XML_TAG_CONVERSATION tag, hence attributes can be read. 242 */ createFromXml(XmlPullParser parser)243 public static BluetoothMapConvoContactElement createFromXml(XmlPullParser parser) 244 throws ParseException, XmlPullParserException, IOException { 245 int count = parser.getAttributeCount(); 246 BluetoothMapConvoContactElement newElement; 247 if (count < 1) { 248 throw new IllegalArgumentException( 249 XML_TAG_CONVOCONTACT + " is not decorated with attributes"); 250 } 251 newElement = new BluetoothMapConvoContactElement(); 252 for (int i = 0; i < count; i++) { 253 String attributeName = parser.getAttributeName(i).trim(); 254 String attributeValue = parser.getAttributeValue(i); 255 if (attributeName.equalsIgnoreCase(XML_ATT_UCI)) { 256 newElement.mUci = attributeValue; 257 } else if (attributeName.equalsIgnoreCase(XML_ATT_NAME)) { 258 newElement.mName = attributeValue; 259 } else if (attributeName.equalsIgnoreCase(XML_ATT_DISPLAY_NAME)) { 260 newElement.mDisplayName = attributeValue; 261 } else if (attributeName.equalsIgnoreCase(XML_ATT_CHAT_STATE)) { 262 newElement.setChatState(attributeValue); 263 } else if (attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) { 264 newElement.setLastActivity(attributeValue); 265 } else if (attributeName.equalsIgnoreCase(XML_ATT_X_BT_UID)) { 266 newElement.setBtUid(SignedLongLong.fromString(attributeValue)); 267 } else if (attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_AVAILABILITY)) { 268 newElement.mPresenceAvailability = Integer.parseInt(attributeValue); 269 } else if (attributeName.equalsIgnoreCase(XML_ATT_PRESENCE_STATUS)) { 270 newElement.setPresenceStatus(attributeValue); 271 } else if (attributeName.equalsIgnoreCase(XML_ATT_PRIORITY)) { 272 newElement.setPriority(Integer.parseInt(attributeValue)); 273 } else { 274 Log.w(TAG, "Unknown XML attribute: " + parser.getAttributeName(i)); 275 } 276 } 277 parser.nextTag(); // Consume the end-tag 278 return newElement; 279 } 280 281 @Override equals(Object obj)282 public boolean equals(Object obj) { 283 if (this == obj) { 284 return true; 285 } 286 if (!(obj instanceof BluetoothMapConvoContactElement other)) { 287 return false; 288 } 289 290 // Skip comparing auto assigned value `mBtUid`. Equals is only used for test 291 292 if (mChatState != other.mChatState) { 293 return false; 294 } 295 if (!Objects.equals(mDisplayName, other.mDisplayName)) { 296 return false; 297 } 298 299 // Skip comparing auto assigned value `mId`. Equals is only used for test 300 301 if (mLastActivity != other.mLastActivity) { 302 return false; 303 } 304 if (!Objects.equals(mName, other.mName)) { 305 return false; 306 } 307 if (mPresenceAvailability != other.mPresenceAvailability) { 308 return false; 309 } 310 if (!Objects.equals(mPresenceStatus, other.mPresenceStatus)) { 311 return false; 312 } 313 if (mPriority != other.mPriority) { 314 return false; 315 } 316 return true; 317 } 318 319 @Override hashCode()320 public int hashCode() { 321 return Objects.hash( 322 mChatState, 323 mDisplayName, 324 mLastActivity, 325 mName, 326 mPresenceAvailability, 327 mPresenceStatus, 328 mPriority); 329 } 330 } 331