Home | History | Annotate | Download | only in map
      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 java.io.IOException;
     18 import java.io.InputStream;
     19 import java.io.StringWriter;
     20 import java.io.UnsupportedEncodingException;
     21 import java.util.HashMap;
     22 import java.util.Locale;
     23 
     24 import org.xmlpull.v1.XmlPullParser;
     25 import org.xmlpull.v1.XmlPullParserException;
     26 import org.xmlpull.v1.XmlSerializer;
     27 
     28 import android.util.Log;
     29 import android.util.Xml;
     30 
     31 import com.android.internal.util.FastXmlSerializer;
     32 import com.android.internal.util.XmlUtils;
     33 
     34 
     35 /**
     36  * Class to contain a single folder element representation.
     37  *
     38  */
     39 public class BluetoothMapFolderElement implements Comparable<BluetoothMapFolderElement>{
     40     private String mName;
     41     private BluetoothMapFolderElement mParent = null;
     42     private long mFolderId = -1;
     43     private boolean mHasSmsMmsContent = false;
     44     private boolean mHasImContent = false;
     45     private boolean mHasEmailContent = false;
     46 
     47     private boolean mIgnore = false;
     48 
     49     private HashMap<String, BluetoothMapFolderElement> mSubFolders;
     50 
     51     private static final boolean D = BluetoothMapService.DEBUG;
     52     private static final boolean V = BluetoothMapService.VERBOSE;
     53 
     54     private final static String TAG = "BluetoothMapFolderElement";
     55 
     56     public BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent){
     57         this.mName = name;
     58         this.mParent = parrent;
     59         mSubFolders = new HashMap<String, BluetoothMapFolderElement>();
     60     }
     61 
     62     public void setIngore(boolean ignore) {
     63         mIgnore = ignore;
     64     }
     65 
     66     public boolean shouldIgnore() {
     67         return mIgnore;
     68     }
     69 
     70     public String getName() {
     71         return mName;
     72     }
     73 
     74     public boolean hasSmsMmsContent(){
     75         return mHasSmsMmsContent;
     76     }
     77 
     78     public long getFolderId(){
     79         return mFolderId;
     80     }
     81     public boolean hasEmailContent(){
     82         return mHasEmailContent;
     83     }
     84 
     85     public void setFolderId(long folderId) {
     86         this.mFolderId = folderId;
     87     }
     88     public void setHasSmsMmsContent(boolean hasSmsMmsContent) {
     89         this.mHasSmsMmsContent = hasSmsMmsContent;
     90     }
     91     public void setHasEmailContent(boolean hasEmailContent) {
     92         this.mHasEmailContent = hasEmailContent;
     93     }
     94     public void setHasImContent(boolean hasImContent) {
     95         this.mHasImContent = hasImContent;
     96     }
     97 
     98     public boolean hasImContent(){
     99         return mHasImContent;
    100     }
    101 
    102     /**
    103      * Fetch the parent folder.
    104      * @return the parent folder or null if we are at the root folder.
    105      */
    106     public BluetoothMapFolderElement getParent() {
    107         return mParent;
    108     }
    109 
    110     /**
    111      * Build the full path to this folder
    112      * @return a string representing the full path.
    113      */
    114     public String getFullPath() {
    115         StringBuilder sb = new StringBuilder(mName);
    116         BluetoothMapFolderElement current = mParent;
    117         while(current != null) {
    118             if(current.getParent() != null) {
    119                 sb.insert(0, current.mName + "/");
    120             }
    121             current = current.getParent();
    122         }
    123         //sb.insert(0, "/"); Should this be included? The MAP spec. do not include it in examples.
    124         return sb.toString();
    125     }
    126 
    127 
    128     public BluetoothMapFolderElement getFolderByName(String name) {
    129         BluetoothMapFolderElement folderElement = this.getRoot();
    130         folderElement = folderElement.getSubFolder("telecom");
    131         folderElement = folderElement.getSubFolder("msg");
    132         folderElement = folderElement.getSubFolder(name);
    133         if (folderElement != null && folderElement.getFolderId() == -1 )
    134             folderElement = null;
    135         return folderElement;
    136     }
    137 
    138     public BluetoothMapFolderElement getFolderById(long id) {
    139         return getFolderById(id, this);
    140     }
    141 
    142     public static BluetoothMapFolderElement getFolderById(long id,
    143             BluetoothMapFolderElement folderStructure) {
    144         if(folderStructure == null) {
    145             return null;
    146         }
    147         return findFolderById(id, folderStructure.getRoot());
    148     }
    149 
    150     private static BluetoothMapFolderElement findFolderById(long id,
    151             BluetoothMapFolderElement folder) {
    152         if(folder.getFolderId() == id) {
    153             return folder;
    154         }
    155         /* Else */
    156         for(BluetoothMapFolderElement subFolder : folder.mSubFolders.values().toArray(
    157                 new BluetoothMapFolderElement[folder.mSubFolders.size()]))
    158         {
    159             BluetoothMapFolderElement ret = findFolderById(id, subFolder);
    160             if(ret != null) {
    161                 return ret;
    162             }
    163         }
    164         return null;
    165     }
    166 
    167 
    168     /**
    169      * Fetch the root folder.
    170      * @return the root folder.
    171      */
    172     public BluetoothMapFolderElement getRoot() {
    173         BluetoothMapFolderElement rootFolder = this;
    174         while(rootFolder.getParent() != null)
    175             rootFolder = rootFolder.getParent();
    176         return rootFolder;
    177     }
    178 
    179     /**
    180      * Add a virtual folder.
    181      * @param name the name of the folder to add.
    182      * @return the added folder element.
    183      */
    184     public BluetoothMapFolderElement addFolder(String name){
    185         name = name.toLowerCase(Locale.US);
    186         BluetoothMapFolderElement newFolder = mSubFolders.get(name);
    187         if(newFolder == null) {
    188             if(D) Log.i(TAG,"addFolder():" + name);
    189             newFolder = new BluetoothMapFolderElement(name, this);
    190             mSubFolders.put(name, newFolder);
    191         } else {
    192             if(D) Log.i(TAG,"addFolder():" + name + " already added");
    193         }
    194         return newFolder;
    195     }
    196 
    197     /**
    198      * Add a sms/mms folder.
    199      * @param name the name of the folder to add.
    200      * @return the added folder element.
    201      */
    202     public BluetoothMapFolderElement addSmsMmsFolder(String name){
    203         if(D) Log.i(TAG,"addSmsMmsFolder()");
    204         BluetoothMapFolderElement newFolder = addFolder(name);
    205         newFolder.setHasSmsMmsContent(true);
    206         return newFolder;
    207     }
    208 
    209     /**
    210      * Add a im folder.
    211      * @param name the name of the folder to add.
    212      * @return the added folder element.
    213      */
    214     public BluetoothMapFolderElement addImFolder(String name, long idFolder){
    215         if(D) Log.i(TAG,"addImFolder() id = " + idFolder);
    216         BluetoothMapFolderElement newFolder = addFolder(name);
    217         newFolder.setHasImContent(true);
    218         newFolder.setFolderId(idFolder);
    219         return newFolder;
    220     }
    221 
    222     /**
    223      * Add an Email folder.
    224      * @param name the name of the folder to add.
    225      * @return the added folder element.
    226      */
    227     public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId){
    228         if(V) Log.v(TAG,"addEmailFolder() id = " + emailFolderId);
    229         BluetoothMapFolderElement newFolder = addFolder(name);
    230         newFolder.setFolderId(emailFolderId);
    231         newFolder.setHasEmailContent(true);
    232         return newFolder;
    233     }
    234     /**
    235      * Fetch the number of sub folders.
    236      * @return returns the number of sub folders.
    237      */
    238     public int getSubFolderCount(){
    239         return mSubFolders.size();
    240     }
    241 
    242     /**
    243      * Returns the subFolder element matching the supplied folder name.
    244      * @param folderName the name of the subFolder to find.
    245      * @return the subFolder element if found {@code null} otherwise.
    246      */
    247     public BluetoothMapFolderElement getSubFolder(String folderName){
    248         return mSubFolders.get(folderName.toLowerCase());
    249     }
    250 
    251     public byte[] encode(int offset, int count) throws UnsupportedEncodingException {
    252         StringWriter sw = new StringWriter();
    253         XmlSerializer xmlMsgElement = new FastXmlSerializer();
    254         int i, stopIndex;
    255         // We need index based access to the subFolders
    256         BluetoothMapFolderElement[] folders = mSubFolders.values().toArray(new BluetoothMapFolderElement[mSubFolders.size()]);
    257 
    258         if(offset > mSubFolders.size())
    259             throw new IllegalArgumentException("FolderListingEncode: offset > subFolders.size()");
    260 
    261         stopIndex = offset + count;
    262         if(stopIndex > mSubFolders.size())
    263             stopIndex = mSubFolders.size();
    264 
    265         try {
    266             xmlMsgElement.setOutput(sw);
    267             xmlMsgElement.startDocument("UTF-8", true);
    268             xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    269             xmlMsgElement.startTag(null, "folder-listing");
    270             xmlMsgElement.attribute(null, "version", BluetoothMapUtils.MAP_V10_STR);
    271             for(i = offset; i<stopIndex; i++)
    272             {
    273                 xmlMsgElement.startTag(null, "folder");
    274                 xmlMsgElement.attribute(null, "name", folders[i].getName());
    275                 xmlMsgElement.endTag(null, "folder");
    276             }
    277             xmlMsgElement.endTag(null, "folder-listing");
    278             xmlMsgElement.endDocument();
    279         } catch (IllegalArgumentException e) {
    280             if(D) Log.w(TAG,e);
    281             throw new IllegalArgumentException("error encoding folderElement");
    282         } catch (IllegalStateException e) {
    283             if(D) Log.w(TAG,e);
    284             throw new IllegalArgumentException("error encoding folderElement");
    285         } catch (IOException e) {
    286             if(D) Log.w(TAG,e);
    287             throw new IllegalArgumentException("error encoding folderElement");
    288         }
    289         return sw.toString().getBytes("UTF-8");
    290     }
    291 
    292     /* The functions below are useful for implementing a MAP client, reusing the object.
    293      * Currently they are only used for test purposes.
    294      * */
    295 
    296     /**
    297      * Append sub folders from an XML document as specified in the MAP specification.
    298      * Attributes will be inherited from parent folder - with regards to message types in the
    299      * folder.
    300      * @param xmlDocument - InputStream with the document
    301      *
    302      * @throws XmlPullParserException
    303      * @throws IOException
    304      */
    305     public void appendSubfolders(InputStream xmlDocument)
    306             throws XmlPullParserException, IOException {
    307         try {
    308             XmlPullParser parser = Xml.newPullParser();
    309             int type;
    310             parser.setInput(xmlDocument, "UTF-8");
    311 
    312             // First find the folder-listing
    313             while((type=parser.next()) != XmlPullParser.END_TAG
    314                     && type != XmlPullParser.END_DOCUMENT ) {
    315                 // Skip until we get a start tag
    316                 if (parser.getEventType() != XmlPullParser.START_TAG) {
    317                     continue;
    318                 }
    319                 // Skip until we get a folder-listing tag
    320                 String name = parser.getName();
    321                 if(!name.equalsIgnoreCase("folder-listing")) {
    322                     if(D) Log.i(TAG,"Unknown XML tag: " + name);
    323                     XmlUtils.skipCurrentTag(parser);
    324                 }
    325                 readFolders(parser);
    326             }
    327         } finally {
    328             xmlDocument.close();
    329         }
    330     }
    331 
    332     /**
    333      * Parses folder elements, and add to mSubFolders.
    334      * @param parser the Xml Parser currently pointing to an folder-listing tag.
    335      * @throws XmlPullParserException
    336      * @throws IOException
    337      */
    338     public void readFolders(XmlPullParser parser)
    339             throws XmlPullParserException, IOException {
    340         int type;
    341         if(D) Log.i(TAG,"readFolders(): ");
    342         while((type=parser.next()) != XmlPullParser.END_TAG
    343                 && type != XmlPullParser.END_DOCUMENT ) {
    344             // Skip until we get a start tag
    345             if (parser.getEventType() != XmlPullParser.START_TAG) {
    346                 continue;
    347             }
    348             // Skip until we get a folder-listing tag
    349             String name = parser.getName();
    350             if(name.trim().equalsIgnoreCase("folder") == false) {
    351                 if(D) Log.i(TAG,"Unknown XML tag: " + name);
    352                 XmlUtils.skipCurrentTag(parser);
    353                 continue;
    354             }
    355             int count = parser.getAttributeCount();
    356             for (int i = 0; i<count; i++) {
    357                 if(parser.getAttributeName(i).trim().equalsIgnoreCase("name")) {
    358                     // We found a folder, append to sub folders.
    359                     BluetoothMapFolderElement element =
    360                             addFolder(parser.getAttributeValue(i).trim());
    361                     element.setHasEmailContent(mHasEmailContent);
    362                     element.setHasImContent(mHasImContent);
    363                     element.setHasSmsMmsContent(mHasSmsMmsContent);
    364                 } else {
    365                     if(D) Log.i(TAG,"Unknown XML attribute: " + parser.getAttributeName(i));
    366                 }
    367             }
    368             parser.nextTag();
    369         }
    370     }
    371 
    372     /**
    373      * Recursive compare of all folder names
    374      */
    375     @Override
    376     public int compareTo(BluetoothMapFolderElement another) {
    377         if(another == null) return 1;
    378         int ret = mName.compareToIgnoreCase(another.mName);
    379         // TODO: Do we want to add compare of folder type?
    380         if(ret == 0) {
    381             ret = mSubFolders.size() - another.mSubFolders.size();
    382             if(ret == 0) {
    383                 // Compare all sub folder elements (will do nothing if mSubFolders is empty)
    384                 for(BluetoothMapFolderElement subfolder : mSubFolders.values()) {
    385                     BluetoothMapFolderElement subfolderAnother =
    386                             another.mSubFolders.get(subfolder.getName());
    387                     if(subfolderAnother == null) {
    388                         if(D) Log.i(TAG, subfolder.getFullPath() + " not in another");
    389                         return 1;
    390                     }
    391                     ret = subfolder.compareTo(subfolderAnother);
    392                     if(ret != 0) {
    393                         if(D) Log.i(TAG, subfolder.getFullPath() + " filed compareTo()");
    394                         return ret;
    395                     }
    396                 }
    397             } else {
    398                 if(D) Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size() +
    399                         " another.mSubFolders.size(): " + another.mSubFolders.size());
    400             }
    401         } else {
    402             if(D) Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName);
    403         }
    404         return ret;
    405     }
    406 }
    407