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