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