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 BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent)56 public BluetoothMapFolderElement( String name, BluetoothMapFolderElement parrent){ 57 this.mName = name; 58 this.mParent = parrent; 59 mSubFolders = new HashMap<String, BluetoothMapFolderElement>(); 60 } 61 setIngore(boolean ignore)62 public void setIngore(boolean ignore) { 63 mIgnore = ignore; 64 } 65 shouldIgnore()66 public boolean shouldIgnore() { 67 return mIgnore; 68 } 69 getName()70 public String getName() { 71 return mName; 72 } 73 hasSmsMmsContent()74 public boolean hasSmsMmsContent(){ 75 return mHasSmsMmsContent; 76 } 77 getFolderId()78 public long getFolderId(){ 79 return mFolderId; 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 } setHasSmsMmsContent(boolean hasSmsMmsContent)88 public void setHasSmsMmsContent(boolean hasSmsMmsContent) { 89 this.mHasSmsMmsContent = hasSmsMmsContent; 90 } setHasEmailContent(boolean hasEmailContent)91 public void setHasEmailContent(boolean hasEmailContent) { 92 this.mHasEmailContent = hasEmailContent; 93 } setHasImContent(boolean hasImContent)94 public void setHasImContent(boolean hasImContent) { 95 this.mHasImContent = hasImContent; 96 } 97 hasImContent()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 */ getParent()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 */ getFullPath()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 getFolderByName(String name)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 getFolderById(long id)138 public BluetoothMapFolderElement getFolderById(long id) { 139 return getFolderById(id, this); 140 } 141 getFolderById(long id, BluetoothMapFolderElement folderStructure)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 findFolderById(long id, BluetoothMapFolderElement folder)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 */ getRoot()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 */ addFolder(String name)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 */ addSmsMmsFolder(String name)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 */ addImFolder(String name, long idFolder)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 */ addEmailFolder(String name, long emailFolderId)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 */ getSubFolderCount()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 */ getSubFolder(String folderName)247 public BluetoothMapFolderElement getSubFolder(String folderName){ 248 return mSubFolders.get(folderName.toLowerCase()); 249 } 250 encode(int offset, int count)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 */ appendSubfolders(InputStream xmlDocument)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 */ readFolders(XmlPullParser parser)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 compareTo(BluetoothMapFolderElement another)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