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