• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright (C) 2013 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 import com.android.bluetooth.Utils;
21 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
22 
23 import org.xmlpull.v1.XmlPullParser;
24 import org.xmlpull.v1.XmlPullParserException;
25 import org.xmlpull.v1.XmlSerializer;
26 
27 import java.io.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.text.ParseException;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Date;
33 import java.util.List;
34 
35 public class BluetoothMapConvoListingElement
36         implements Comparable<BluetoothMapConvoListingElement> {
37 
38     public static final String XML_TAG_CONVERSATION = "conversation";
39     private static final String XML_ATT_LAST_ACTIVITY = "last_activity";
40     private static final String XML_ATT_NAME = "name";
41     private static final String XML_ATT_ID = "id";
42     private static final String XML_ATT_READ = "readstatus";
43     private static final String XML_ATT_VERSION_COUNTER = "version_counter";
44     private static final String XML_ATT_SUMMARY = "summary";
45     private static final String TAG = "BluetoothMapConvoListingElement";
46     private static final boolean D = BluetoothMapService.DEBUG;
47     private static final boolean V = BluetoothMapService.VERBOSE;
48 
49     private SignedLongLong mId = null;
50     private String mName = ""; //title of the conversation #REQUIRED, but allowed empty
51     private long mLastActivity = -1;
52     private boolean mRead = false;
53     private boolean mReportRead = false; // TODO: Is this needed? - false means UNKNOWN
54     private List<BluetoothMapConvoContactElement> mContacts;
55     private long mVersionCounter = -1;
56     private int mCursorIndex = 0;
57     private TYPE mType = null;
58     private String mSummary = null;
59 
60     // Used only to keep track of changes to convoListVersionCounter;
61     private String mSmsMmsContacts = null;
62 
getCursorIndex()63     public int getCursorIndex() {
64         return mCursorIndex;
65     }
66 
setCursorIndex(int cursorIndex)67     public void setCursorIndex(int cursorIndex) {
68         this.mCursorIndex = cursorIndex;
69         if (D) {
70             Log.d(TAG, "setCursorIndex: " + cursorIndex);
71         }
72     }
73 
getVersionCounter()74     public long getVersionCounter() {
75         return mVersionCounter;
76     }
77 
setVersionCounter(long vcount)78     public void setVersionCounter(long vcount) {
79         if (D) {
80             Log.d(TAG, "setVersionCounter: " + vcount);
81         }
82         this.mVersionCounter = vcount;
83     }
84 
incrementVersionCounter()85     public void incrementVersionCounter() {
86         mVersionCounter++;
87     }
88 
setVersionCounter(String vcount)89     private void setVersionCounter(String vcount) {
90         if (D) {
91             Log.d(TAG, "setVersionCounter: " + vcount);
92         }
93         try {
94             this.mVersionCounter = Long.parseLong(vcount);
95         } catch (NumberFormatException e) {
96             Log.w(TAG, "unable to parse XML versionCounter:" + vcount);
97             mVersionCounter = -1;
98         }
99     }
100 
getName()101     public String getName() {
102         return mName;
103     }
104 
setName(String name)105     public void setName(String name) {
106         if (D) {
107             Log.d(TAG, "setName: " + name);
108         }
109         this.mName = name;
110     }
111 
getType()112     public TYPE getType() {
113         return mType;
114     }
115 
setType(TYPE type)116     public void setType(TYPE type) {
117         this.mType = type;
118     }
119 
getContacts()120     public List<BluetoothMapConvoContactElement> getContacts() {
121         return mContacts;
122     }
123 
setContacts(List<BluetoothMapConvoContactElement> contacts)124     public void setContacts(List<BluetoothMapConvoContactElement> contacts) {
125         this.mContacts = contacts;
126     }
127 
addContact(BluetoothMapConvoContactElement contact)128     public void addContact(BluetoothMapConvoContactElement contact) {
129         if (mContacts == null) {
130             mContacts = new ArrayList<BluetoothMapConvoContactElement>();
131         }
132         mContacts.add(contact);
133     }
134 
removeContact(BluetoothMapConvoContactElement contact)135     public void removeContact(BluetoothMapConvoContactElement contact) {
136         mContacts.remove(contact);
137     }
138 
removeContact(int index)139     public void removeContact(int index) {
140         mContacts.remove(index);
141     }
142 
143 
getLastActivity()144     public long getLastActivity() {
145         return mLastActivity;
146     }
147 
getLastActivityString()148     public String getLastActivityString() {
149         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
150         Date date = new Date(mLastActivity);
151         return format.format(date); // Format to YYYYMMDDTHHMMSS local time
152     }
153 
setLastActivity(long last)154     public void setLastActivity(long last) {
155         if (D) {
156             Log.d(TAG, "setLastActivity: " + last);
157         }
158         this.mLastActivity = last;
159     }
160 
setLastActivity(String lastActivity)161     public void setLastActivity(String lastActivity) throws ParseException {
162         // TODO: Encode with time-zone if MCE requests it
163         SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
164         Date date = format.parse(lastActivity);
165         this.mLastActivity = date.getTime();
166     }
167 
getRead()168     public String getRead() {
169         if (!mReportRead) {
170             return "UNKNOWN";
171         }
172         return (mRead ? "READ" : "UNREAD");
173     }
174 
getReadBool()175     public boolean getReadBool() {
176         return mRead;
177     }
178 
setRead(boolean read, boolean reportRead)179     public void setRead(boolean read, boolean reportRead) {
180         this.mRead = read;
181         if (D) {
182             Log.d(TAG, "setRead: " + read);
183         }
184         this.mReportRead = reportRead;
185     }
186 
setRead(String value)187     private void setRead(String value) {
188         if (value.trim().equalsIgnoreCase("yes")) {
189             mRead = true;
190         } else {
191             mRead = false;
192         }
193         mReportRead = true;
194     }
195 
196     /**
197      * Set the conversation ID
198      * @param type 0 if the thread ID is valid across all message types in the instance - else
199      * use one of the CONVO_ID_xxx types.
200      * @param threadId the conversation ID
201      */
setConvoId(long type, long threadId)202     public void setConvoId(long type, long threadId) {
203         this.mId = new SignedLongLong(threadId, type);
204         if (D) {
205             Log.d(TAG, "setConvoId: " + threadId + " type:" + type);
206         }
207     }
208 
getConvoId()209     public String getConvoId() {
210         return mId.toHexString();
211     }
212 
getCpConvoId()213     public long getCpConvoId() {
214         return mId.getLeastSignificantBits();
215     }
216 
setSummary(String summary)217     public void setSummary(String summary) {
218         mSummary = summary;
219     }
220 
getFullSummary()221     public String getFullSummary() {
222         return mSummary;
223     }
224 
225     /* Get a valid UTF-8 string of maximum 256 bytes */
getSummary()226     private String getSummary() {
227         if (mSummary != null) {
228             try {
229                 return BluetoothMapUtils.truncateUtf8StringToString(mSummary, 256);
230             } catch (UnsupportedEncodingException e) {
231                 // This cannot happen on an Android platform - UTF-8 is mandatory
232                 Log.e(TAG, "Missing UTF-8 support on platform", e);
233             }
234         }
235         return null;
236     }
237 
getSmsMmsContacts()238     public String getSmsMmsContacts() {
239         return mSmsMmsContacts;
240     }
241 
setSmsMmsContacts(String smsMmsContacts)242     public void setSmsMmsContacts(String smsMmsContacts) {
243         mSmsMmsContacts = smsMmsContacts;
244     }
245 
246     @Override
compareTo(BluetoothMapConvoListingElement e)247     public int compareTo(BluetoothMapConvoListingElement e) {
248         if (this.mLastActivity < e.mLastActivity) {
249             return 1;
250         } else if (this.mLastActivity > e.mLastActivity) {
251             return -1;
252         } else {
253             return 0;
254         }
255     }
256 
257     /* Encode the MapMessageListingElement into the StringBuilder reference.
258      * Here we have taken the choice not to report empty attributes, to reduce the
259      * amount of data to be transfered over BT. */
encode(XmlSerializer xmlConvoElement)260     public void encode(XmlSerializer xmlConvoElement)
261             throws IllegalArgumentException, IllegalStateException, IOException {
262 
263         // contruct the XML tag for a single conversation in the convolisting
264         xmlConvoElement.startTag(null, XML_TAG_CONVERSATION);
265         xmlConvoElement.attribute(null, XML_ATT_ID, mId.toHexString());
266         if (mName != null) {
267             xmlConvoElement.attribute(null, XML_ATT_NAME,
268                     BluetoothMapUtils.stripInvalidChars(mName));
269         }
270         if (mLastActivity != -1) {
271             xmlConvoElement.attribute(null, XML_ATT_LAST_ACTIVITY, getLastActivityString());
272         }
273         // Even though this is implied, the value "UNKNOWN" kind of indicated it is required.
274         if (mReportRead) {
275             xmlConvoElement.attribute(null, XML_ATT_READ, getRead());
276         }
277         if (mVersionCounter != -1) {
278             xmlConvoElement.attribute(null, XML_ATT_VERSION_COUNTER,
279                     Long.toString(getVersionCounter()));
280         }
281         if (mSummary != null) {
282             xmlConvoElement.attribute(null, XML_ATT_SUMMARY, getSummary());
283         }
284         if (mContacts != null) {
285             for (BluetoothMapConvoContactElement contact : mContacts) {
286                 contact.encode(xmlConvoElement);
287             }
288         }
289         xmlConvoElement.endTag(null, XML_TAG_CONVERSATION);
290 
291     }
292 
293     /**
294      * Consumes a conversation tag. It is expected that the parser is beyond the start-tag event,
295      * with the name "conversation".
296      * @param parser
297      * @return
298      * @throws XmlPullParserException
299      * @throws IOException
300      */
createFromXml(XmlPullParser parser)301     public static BluetoothMapConvoListingElement createFromXml(XmlPullParser parser)
302             throws XmlPullParserException, IOException, ParseException {
303         BluetoothMapConvoListingElement newElement = new BluetoothMapConvoListingElement();
304         int count = parser.getAttributeCount();
305         int type;
306         for (int i = 0; i < count; i++) {
307             String attributeName = parser.getAttributeName(i).trim();
308             String attributeValue = parser.getAttributeValue(i);
309             if (attributeName.equalsIgnoreCase(XML_ATT_ID)) {
310                 newElement.mId = SignedLongLong.fromString(attributeValue);
311             } else if (attributeName.equalsIgnoreCase(XML_ATT_NAME)) {
312                 newElement.mName = attributeValue;
313             } else if (attributeName.equalsIgnoreCase(XML_ATT_LAST_ACTIVITY)) {
314                 newElement.setLastActivity(attributeValue);
315             } else if (attributeName.equalsIgnoreCase(XML_ATT_READ)) {
316                 newElement.setRead(attributeValue);
317             } else if (attributeName.equalsIgnoreCase(XML_ATT_VERSION_COUNTER)) {
318                 newElement.setVersionCounter(attributeValue);
319             } else if (attributeName.equalsIgnoreCase(XML_ATT_SUMMARY)) {
320                 newElement.setSummary(attributeValue);
321             } else {
322                 if (D) {
323                     Log.i(TAG, "Unknown XML attribute: " + parser.getAttributeName(i));
324                 }
325             }
326         }
327 
328         // Now determine if we get an end-tag, or a new start tag for contacts
329         while ((type = parser.next()) != XmlPullParser.END_TAG
330                 && type != XmlPullParser.END_DOCUMENT) {
331             // Skip until we get a start tag
332             if (parser.getEventType() != XmlPullParser.START_TAG) {
333                 continue;
334             }
335             // Skip until we get a convocontact tag
336             String name = parser.getName().trim();
337             if (name.equalsIgnoreCase(BluetoothMapConvoContactElement.XML_TAG_CONVOCONTACT)) {
338                 newElement.addContact(BluetoothMapConvoContactElement.createFromXml(parser));
339             } else {
340                 if (D) {
341                     Log.i(TAG, "Unknown XML tag: " + name);
342                 }
343                 Utils.skipCurrentTag(parser);
344                 continue;
345             }
346         }
347         // As we have extracted all attributes, we should expect an end-tag
348         // parser.nextTag(); // consume the end-tag
349         // TODO: Is this needed? - we should already be at end-tag, as this is the top condition
350 
351         return newElement;
352     }
353 
354     @Override
equals(Object obj)355     public boolean equals(Object obj) {
356         if (this == obj) {
357             return true;
358         }
359         if (obj == null) {
360             return false;
361         }
362         if (getClass() != obj.getClass()) {
363             return false;
364         }
365         BluetoothMapConvoListingElement other = (BluetoothMapConvoListingElement) obj;
366         if (mContacts == null) {
367             if (other.mContacts != null) {
368                 return false;
369             }
370         } else if (!mContacts.equals(other.mContacts)) {
371             return false;
372         }
373         /* As we use equals only for test, we don't compare auto assigned values
374          * if (mId == null) {
375             if (other.mId != null) {
376                 return false;
377             }
378         } else if (!mId.equals(other.mId)) {
379             return false;
380         } */
381 
382         if (mLastActivity != other.mLastActivity) {
383             return false;
384         }
385         if (mName == null) {
386             if (other.mName != null) {
387                 return false;
388             }
389         } else if (!mName.equals(other.mName)) {
390             return false;
391         }
392         if (mRead != other.mRead) {
393             return false;
394         }
395         return true;
396     }
397 
398 /*    @Override
399     public boolean equals(Object o) {
400 
401         return true;
402     };
403     */
404 
405 }
406 
407 
408