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