blob: 560558e1fcdef2b76ef6e26f18f83752fc958208 [file] [log] [blame]
/*
* 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.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
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<BluetoothMapFolderElement> {
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<String, BluetoothMapFolderElement> 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<String, BluetoothMapFolderElement>();
}
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();
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);
}
XmlUtils.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);
}
XmlUtils.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;
}
}