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