/* * Copyright (C) 2015 Samsung System LSI * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.map; import android.util.Log; import android.util.Xml; import com.android.bluetooth.Utils; import com.android.internal.util.FastXmlSerializer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Locale; /** * Class to contain a single folder element representation. * */ public class BluetoothMapFolderElement implements Comparable { private String mName; private BluetoothMapFolderElement mParent = null; private long mFolderId = -1; private boolean mHasSmsMmsContent = false; private boolean mHasImContent = false; private boolean mHasEmailContent = false; private boolean mIgnore = false; private HashMap mSubFolders; private static final boolean D = BluetoothMapService.DEBUG; private static final boolean V = BluetoothMapService.VERBOSE; private static final String TAG = "BluetoothMapFolderElement"; public BluetoothMapFolderElement(String name, BluetoothMapFolderElement parrent) { this.mName = name; this.mParent = parrent; mSubFolders = new HashMap(); } public void setIngore(boolean ignore) { mIgnore = ignore; } public boolean shouldIgnore() { return mIgnore; } public String getName() { return mName; } public boolean hasSmsMmsContent() { return mHasSmsMmsContent; } public long getFolderId() { return mFolderId; } public boolean hasEmailContent() { return mHasEmailContent; } public void setFolderId(long folderId) { this.mFolderId = folderId; } public void setHasSmsMmsContent(boolean hasSmsMmsContent) { this.mHasSmsMmsContent = hasSmsMmsContent; } public void setHasEmailContent(boolean hasEmailContent) { this.mHasEmailContent = hasEmailContent; } public void setHasImContent(boolean hasImContent) { this.mHasImContent = hasImContent; } public boolean hasImContent() { return mHasImContent; } /** * Fetch the parent folder. * @return the parent folder or null if we are at the root folder. */ public BluetoothMapFolderElement getParent() { return mParent; } /** * Build the full path to this folder * @return a string representing the full path. */ public String getFullPath() { StringBuilder sb = new StringBuilder(mName); BluetoothMapFolderElement current = mParent; while (current != null) { if (current.getParent() != null) { sb.insert(0, current.mName + "/"); } current = current.getParent(); } //sb.insert(0, "/"); Should this be included? The MAP spec. do not include it in examples. return sb.toString(); } public BluetoothMapFolderElement getFolderByName(String name) { BluetoothMapFolderElement folderElement = this.getRoot(); folderElement = folderElement.getSubFolder("telecom"); folderElement = folderElement.getSubFolder("msg"); folderElement = folderElement.getSubFolder(name); if (folderElement != null && folderElement.getFolderId() == -1) { folderElement = null; } return folderElement; } public BluetoothMapFolderElement getFolderById(long id) { return getFolderById(id, this); } public static BluetoothMapFolderElement getFolderById(long id, BluetoothMapFolderElement folderStructure) { if (folderStructure == null) { return null; } return findFolderById(id, folderStructure.getRoot()); } private static BluetoothMapFolderElement findFolderById(long id, BluetoothMapFolderElement folder) { if (folder.getFolderId() == id) { return folder; } /* Else */ for (BluetoothMapFolderElement subFolder : folder.mSubFolders.values() .toArray(new BluetoothMapFolderElement[folder.mSubFolders.size()])) { BluetoothMapFolderElement ret = findFolderById(id, subFolder); if (ret != null) { return ret; } } return null; } /** * Fetch the root folder. * @return the root folder. */ public BluetoothMapFolderElement getRoot() { BluetoothMapFolderElement rootFolder = this; while (rootFolder.getParent() != null) { rootFolder = rootFolder.getParent(); } return rootFolder; } /** * Add a virtual folder. * @param name the name of the folder to add. * @return the added folder element. */ public BluetoothMapFolderElement addFolder(String name) { name = name.toLowerCase(Locale.US); BluetoothMapFolderElement newFolder = mSubFolders.get(name); if (newFolder == null) { if (D) { Log.i(TAG, "addFolder():" + name); } newFolder = new BluetoothMapFolderElement(name, this); mSubFolders.put(name, newFolder); } else { if (D) { Log.i(TAG, "addFolder():" + name + " already added"); } } return newFolder; } /** * Add a sms/mms folder. * @param name the name of the folder to add. * @return the added folder element. */ public BluetoothMapFolderElement addSmsMmsFolder(String name) { if (D) { Log.i(TAG, "addSmsMmsFolder()"); } BluetoothMapFolderElement newFolder = addFolder(name); newFolder.setHasSmsMmsContent(true); return newFolder; } /** * Add a im folder. * @param name the name of the folder to add. * @return the added folder element. */ public BluetoothMapFolderElement addImFolder(String name, long idFolder) { if (D) { Log.i(TAG, "addImFolder() id = " + idFolder); } BluetoothMapFolderElement newFolder = addFolder(name); newFolder.setHasImContent(true); newFolder.setFolderId(idFolder); return newFolder; } /** * Add an Email folder. * @param name the name of the folder to add. * @return the added folder element. */ public BluetoothMapFolderElement addEmailFolder(String name, long emailFolderId) { if (V) { Log.v(TAG, "addEmailFolder() id = " + emailFolderId); } BluetoothMapFolderElement newFolder = addFolder(name); newFolder.setFolderId(emailFolderId); newFolder.setHasEmailContent(true); return newFolder; } /** * Fetch the number of sub folders. * @return returns the number of sub folders. */ public int getSubFolderCount() { return mSubFolders.size(); } /** * Returns the subFolder element matching the supplied folder name. * @param folderName the name of the subFolder to find. * @return the subFolder element if found {@code null} otherwise. */ public BluetoothMapFolderElement getSubFolder(String folderName) { return mSubFolders.get(folderName.toLowerCase()); } public byte[] encode(int offset, int count) throws UnsupportedEncodingException { StringWriter sw = new StringWriter(); XmlSerializer xmlMsgElement = new FastXmlSerializer(0); int i, stopIndex; // We need index based access to the subFolders BluetoothMapFolderElement[] folders = mSubFolders.values().toArray(new BluetoothMapFolderElement[mSubFolders.size()]); if (offset > mSubFolders.size()) { throw new IllegalArgumentException("FolderListingEncode: offset > subFolders.size()"); } stopIndex = offset + count; if (stopIndex > mSubFolders.size()) { stopIndex = mSubFolders.size(); } try { xmlMsgElement.setOutput(sw); xmlMsgElement.startDocument("UTF-8", true); xmlMsgElement.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); xmlMsgElement.startTag(null, "folder-listing"); xmlMsgElement.attribute(null, "version", BluetoothMapUtils.MAP_V10_STR); for (i = offset; i < stopIndex; i++) { xmlMsgElement.startTag(null, "folder"); xmlMsgElement.attribute(null, "name", folders[i].getName()); xmlMsgElement.endTag(null, "folder"); } xmlMsgElement.endTag(null, "folder-listing"); xmlMsgElement.endDocument(); } catch (IllegalArgumentException e) { if (D) { Log.w(TAG, e); } throw new IllegalArgumentException("error encoding folderElement"); } catch (IllegalStateException e) { if (D) { Log.w(TAG, e); } throw new IllegalArgumentException("error encoding folderElement"); } catch (IOException e) { if (D) { Log.w(TAG, e); } throw new IllegalArgumentException("error encoding folderElement"); } return sw.toString().getBytes("UTF-8"); } /* The functions below are useful for implementing a MAP client, reusing the object. * Currently they are only used for test purposes. * */ /** * Append sub folders from an XML document as specified in the MAP specification. * Attributes will be inherited from parent folder - with regards to message types in the * folder. * @param xmlDocument - InputStream with the document * * @throws XmlPullParserException * @throws IOException */ public void appendSubfolders(InputStream xmlDocument) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); int type; parser.setInput(xmlDocument, "UTF-8"); // First find the folder-listing while ((type = parser.next()) != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) { // Skip until we get a start tag if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } // Skip until we get a folder-listing tag String name = parser.getName(); if (!name.equalsIgnoreCase("folder-listing")) { if (D) { Log.i(TAG, "Unknown XML tag: " + name); } Utils.skipCurrentTag(parser); } readFolders(parser); } } finally { xmlDocument.close(); } } /** * Parses folder elements, and add to mSubFolders. * @param parser the Xml Parser currently pointing to an folder-listing tag. * @throws XmlPullParserException * @throws IOException */ public void readFolders(XmlPullParser parser) throws XmlPullParserException, IOException { int type; if (D) { Log.i(TAG, "readFolders(): "); } while ((type = parser.next()) != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) { // Skip until we get a start tag if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } // Skip until we get a folder-listing tag String name = parser.getName(); if (!name.trim().equalsIgnoreCase("folder")) { if (D) { Log.i(TAG, "Unknown XML tag: " + name); } Utils.skipCurrentTag(parser); continue; } int count = parser.getAttributeCount(); for (int i = 0; i < count; i++) { if (parser.getAttributeName(i).trim().equalsIgnoreCase("name")) { // We found a folder, append to sub folders. BluetoothMapFolderElement element = addFolder(parser.getAttributeValue(i).trim()); element.setHasEmailContent(mHasEmailContent); element.setHasImContent(mHasImContent); element.setHasSmsMmsContent(mHasSmsMmsContent); } else { if (D) { Log.i(TAG, "Unknown XML attribute: " + parser.getAttributeName(i)); } } } parser.nextTag(); } } /** * Recursive compare of all folder names */ @Override public int compareTo(BluetoothMapFolderElement another) { if (another == null) { return 1; } int ret = mName.compareToIgnoreCase(another.mName); // TODO: Do we want to add compare of folder type? if (ret == 0) { ret = mSubFolders.size() - another.mSubFolders.size(); if (ret == 0) { // Compare all sub folder elements (will do nothing if mSubFolders is empty) for (BluetoothMapFolderElement subfolder : mSubFolders.values()) { BluetoothMapFolderElement subfolderAnother = another.mSubFolders.get(subfolder.getName()); if (subfolderAnother == null) { if (D) { Log.i(TAG, subfolder.getFullPath() + " not in another"); } return 1; } ret = subfolder.compareTo(subfolderAnother); if (ret != 0) { if (D) { Log.i(TAG, subfolder.getFullPath() + " filed compareTo()"); } return ret; } } } else { if (D) { Log.i(TAG, "mSubFolders.size(): " + mSubFolders.size() + " another.mSubFolders.size(): " + another.mSubFolders.size()); } } } else { if (D) { Log.i(TAG, "mName: " + mName + " another.mName: " + another.mName); } } return ret; } }