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