blob: dc9bc43e3a3a99e91f7f6e2d940cd58afafc2f31 [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.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.text.format.DateUtils;
import android.util.Log;
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapMasInstance;
import com.android.bluetooth.mapapi.BluetoothMapContract;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Calendar;
import javax.obex.HeaderSet;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
import javax.obex.ServerRequestHandler;
public class BluetoothMapObexServer extends ServerRequestHandler {
private static final String TAG = "BluetoothMapObexServer";
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
private static final int UUID_LENGTH = 16;
private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
/* OBEX header and value used to detect clients that support threadId in the message listing. */
private static final int THREADED_MAIL_HEADER_ID = 0xFA;
private static final long THREAD_MAIL_KEY = 0x534c5349;
// 128 bit UUID for MAP
private static final byte[] MAP_TARGET = new byte[] {
(byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
(byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
(byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
(byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
};
public static final ParcelUuid MAP =
ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MNS =
ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
public static final ParcelUuid MAS =
ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
/* Message types */
private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
private static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing";
private static final String TYPE_MESSAGE = "x-bt/message";
private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
private static final String TYPE_SET_NOTIFICATION_REGISTRATION
= "x-bt/MAP-NotificationRegistration";
private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate";
private static final String TYPE_GET_MAS_INSTANCE_INFORMATION
= "x-bt/MASInstanceInformation";
private static final String TYPE_SET_OWNER_STATUS = "x-bt/participant";
private static final String TYPE_SET_NOTIFICATION_FILTER
= "x-bt/MAP-notification-filter";
private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200;
private BluetoothMapFolderElement mCurrentFolder;
private BluetoothMapContentObserver mObserver = null;
private Handler mCallback = null;
private Context mContext;
private boolean mIsAborted = false;
BluetoothMapContent mOutContent;
private String mBaseUriString = null;
private long mAccountId = 0;
private BluetoothMapAccountItem mAccount = null;
private Uri mEmailFolderUri = null;
private int mMasId = 0;
private BluetoothMapMasInstance mMasInstance; // TODO: change to interface?
// updated during connect if remote has alternative value
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
private boolean mEnableSmsMms = false;
private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing
// Defaults message version is 1.0 but 1.1+ if feature bit is set
private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
private String mAuthority;
private ContentResolver mResolver;
private ContentProviderClient mProviderClient = null;
public BluetoothMapObexServer(Handler callback,
Context context,
BluetoothMapContentObserver observer,
BluetoothMapMasInstance mas,
BluetoothMapAccountItem account,
boolean enableSmsMms) throws RemoteException {
super();
mCallback = callback;
mContext = context;
mObserver = observer;
mEnableSmsMms = enableSmsMms;
mAccount = account;
mMasId = mas.getMasId();
mMasInstance = mas;
mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask();
if(account != null && account.getProviderAuthority() != null) {
mAccountId = account.getAccountId();
mAuthority = account.getProviderAuthority();
mResolver = mContext.getContentResolver();
if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId);
mBaseUriString = account.mBase_uri + "/";
if (D) Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString);
if (account.getType() == TYPE.EMAIL) {
mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority,
Long.toString(mAccountId));
if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri);
}
mProviderClient = acquireUnstableContentProviderOrThrow();
}
buildFolderStructure(); /* Build the default folder structure, and set
mCurrentFolder to root folder */
mObserver.setFolderStructure(mCurrentFolder.getRoot());
mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance);
}
/**
*
*/
private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException{
ContentProviderClient providerClient =
mResolver.acquireUnstableContentProviderClient(mAuthority);
if (providerClient == null) {
throw new RemoteException("Failed to acquire provider for " + mAuthority);
}
providerClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
return providerClient;
}
/**
* Build the default minimal folder structure, as defined in the MAP specification.
*/
private void buildFolderStructure() throws RemoteException{
mCurrentFolder = new BluetoothMapFolderElement("root", null);//This will be the root element
mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms);
boolean hasIM = false;
boolean hasEmail = false;
if (mAccount != null) {
if (mAccount.getType() == TYPE.IM)
hasIM = true;
if( mAccount.getType() == TYPE.EMAIL)
hasEmail = true;
}
mCurrentFolder.setHasImContent(hasIM);
mCurrentFolder.setHasEmailContent(hasEmail);
BluetoothMapFolderElement tmpFolder;
tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
tmpFolder.setHasImContent(hasIM);
tmpFolder.setHasEmailContent(hasEmail);
tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg
tmpFolder.setHasSmsMmsContent(mEnableSmsMms);
tmpFolder.setHasImContent(hasIM);
tmpFolder.setHasEmailContent(hasEmail);
// Add the mandatory folders
addBaseFolders(tmpFolder);
if(mEnableSmsMms) {
addSmsMmsFolders(tmpFolder);
}
if(hasEmail) {
if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString());
addEmailFolders(tmpFolder);
}
if (hasIM) {
addImFolders(tmpFolder);
}
}
/**
* Add
* @param root
*/
private void addBaseFolders(BluetoothMapFolderElement root) {
root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT);
root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
}
/**
* Add
* @param root
*/
private void addSmsMmsFolders(BluetoothMapFolderElement root) {
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX);
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT);
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED);
root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DRAFT);
}
private void addImFolders(BluetoothMapFolderElement root) throws RemoteException {
// Select all parent folders
root.addImFolder(BluetoothMapContract.FOLDER_NAME_INBOX,
BluetoothMapContract.FOLDER_ID_INBOX); // root/telecom/msg/inbox
root.addImFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX,
BluetoothMapContract.FOLDER_ID_OUTBOX);
root.addImFolder(BluetoothMapContract.FOLDER_NAME_SENT,
BluetoothMapContract.FOLDER_ID_SENT);
root.addImFolder(BluetoothMapContract.FOLDER_NAME_DELETED,
BluetoothMapContract.FOLDER_ID_DELETED);
root.addImFolder(BluetoothMapContract.FOLDER_NAME_DRAFT,
BluetoothMapContract.FOLDER_ID_DRAFT);
}
/**
* Recursively adds folders based on the folders in the email content provider.
* Add a content observer? - to refresh the folder list if any change occurs.
* Consider simply deleting the entire table, and then rebuild using
* buildFolderStructure()
* WARNING: there is no way to notify the client about these changes - hence
* we need to either keep the folder structure constant, disconnect or fail anything
* referring to currentFolder.
* It is unclear what to set as current folder to be able to go one level up...
* The best solution would be to keep the folder structure constant during a connection.
* @param folder the parent folder to which subFolders needs to be added. The
* folder.getFolderId() will be used to query sub-folders.
* Use a parentFolder with id -1 to get all folders from root.
*/
private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
// Select all parent folders
BluetoothMapFolderElement newFolder;
String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID +
" = " + parentFolder.getFolderId();
Cursor c = mProviderClient.query(mEmailFolderUri,
BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null);
try {
if (c != null) {
c.moveToPosition(-1);
while (c.moveToNext()) {
String name = c.getString(c.getColumnIndex(
BluetoothMapContract.FolderColumns.NAME));
long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID));
newFolder = parentFolder.addEmailFolder(name, id);
addEmailFolders(newFolder); // Use recursion to add any sub folders
}
} else {
if (D) Log.d(TAG, "addEmailFolders(): no elements found");
}
} finally {
if (c != null) c.close();
}
}
@Override
public boolean isSrmSupported() {
// TODO: Update based on the transport used
return true;
}
public int getRemoteFeatureMask() {
return mRemoteFeatureMask;
}
public void setRemoteFeatureMask(int mRemoteFeatureMask) {
if(D) Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask));
this.mRemoteFeatureMask = mRemoteFeatureMask;
this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask);
}
@Override
public int onConnect(final HeaderSet request, HeaderSet reply) {
if (D) Log.d(TAG, "onConnect():");
if (V) logHeader(request);
mThreadIdSupport = false; // Always assume not supported at new connect.
mMessageVersion = BluetoothMapUtils.MAP_V10_STR;//always assume version 1.0 to start with
notifyUpdateWakeLock();
Long threadedMailKey = null;
try {
byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
threadedMailKey = (Long)request.getHeader(THREADED_MAIL_HEADER_ID);
if (uuid == null) {
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
if (uuid.length != UUID_LENGTH) {
Log.w(TAG, "Wrong UUID length");
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
for (int i = 0; i < UUID_LENGTH; i++) {
if (uuid[i] != MAP_TARGET[i]) {
Log.w(TAG, "Wrong UUID");
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
}
reply.setHeader(HeaderSet.WHO, uuid);
} catch (IOException e) {
Log.e(TAG,"Exception during onConnect:", e);
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
try {
byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
if (remote != null) {
if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
reply.setHeader(HeaderSet.TARGET, remote);
}
if(threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY)
{
/* If the client provides the correct key we enable threaded e-mail support
* and reply to the client that we support the requested feature.
* This is currently an Android only feature. */
mThreadIdSupport = true;
reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY);
}
} catch (IOException e) {
Log.e(TAG,"Exception during onConnect:", e);
mThreadIdSupport = false;
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
}
if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)
== BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) {
mThreadIdSupport = true;
}
if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT)
== BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT){
mMessageVersion = BluetoothMapUtils.MAP_V11_STR;
}
if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
"MSG_SESSION_ESTABLISHED msg.");
if(mCallback != null) {
Message msg = Message.obtain(mCallback);
msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
msg.sendToTarget();
}
return ResponseCodes.OBEX_HTTP_OK;
}
@Override
public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
if (D) Log.d(TAG, "onDisconnect(): enter");
if (V) logHeader(req);
notifyUpdateWakeLock();
resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
if (mCallback != null) {
Message msg = Message.obtain(mCallback);
msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
msg.sendToTarget();
if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
}
}
@Override
public int onAbort(HeaderSet request, HeaderSet reply) {
if (D) Log.d(TAG, "onAbort(): enter.");
notifyUpdateWakeLock();
mIsAborted = true;
return ResponseCodes.OBEX_HTTP_OK;
}
@Override
public int onPut(final Operation op) {
if (D) Log.d(TAG, "onPut(): enter");
mIsAborted = false;
notifyUpdateWakeLock();
HeaderSet request = null;
String type, name;
byte[] appParamRaw;
BluetoothMapAppParams appParams = null;
try {
request = op.getReceivedHeader();
if (V) logHeader(request);
type = (String)request.getHeader(HeaderSet.TYPE);
name = (String)request.getHeader(HeaderSet.NAME);
appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
if(appParamRaw != null)
appParams = new BluetoothMapAppParams(appParamRaw);
if(D) Log.d(TAG,"type = " + type + ", name = " + name);
if (type.equals(TYPE_MESSAGE_UPDATE)) {
if(V) {
Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
}
return updateInbox();
}else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
if(V) {
Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: "
+ appParams.getNotificationStatus());
}
return mObserver.setNotificationRegistration(appParams.getNotificationStatus());
}else if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
if(V) {
Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+ appParams.getNotificationFilter());
}
mObserver.setNotificationFilter(appParams.getNotificationFilter());
return ResponseCodes.OBEX_HTTP_OK;
} else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
if(V) {
Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: " +
"StatusIndicator: " + appParams.getStatusIndicator()
+ ", StatusValue: " + appParams.getStatusValue()
+ ", ExtentedData: " + "" ); // TODO: appParams.getExtendedImData());
}
return setMessageStatus(name, appParams);
} else if (type.equals(TYPE_MESSAGE)) {
if(V) {
Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent()
+ ", retry: " + appParams.getRetry()
+ ", charset: " + appParams.getCharset());
}
return pushMessage(op, name, appParams, mMessageVersion);
} else if (type.equals(TYPE_SET_OWNER_STATUS)) {
if(V) {
Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
" PresenceAvailability " + appParams.getPresenceAvailability() +
", PresenceStatus: " + appParams.getPresenceStatus() +
", LastActivity: " + appParams.getLastActivityString() +
", ChatStatus: " + appParams.getChatState() +
", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
}
return setOwnerStatus(name, appParams);
}
} catch (RemoteException e){
//reload the providerClient and return error
try {
mProviderClient = acquireUnstableContentProviderOrThrow();
}catch (RemoteException e2){
//should not happen
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}catch (Exception e) {
if(D) {
Log.e(TAG, "Exception occured while handling request",e);
} else {
Log.e(TAG, "Exception occured while handling request");
}
if(mIsAborted) {
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
private int updateInbox() throws RemoteException{
if (mAccount != null) {
BluetoothMapFolderElement inboxFolder = mCurrentFolder.getFolderByName(
BluetoothMapContract.FOLDER_NAME_INBOX);
if (inboxFolder != null) {
long accountId = mAccountId;
if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id="
+ inboxFolder.getFolderId());
final Bundle extras = new Bundle(2);
if (accountId != -1) {
if (D) Log.d(TAG,"updateInbox accountId=" + accountId);
extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID,
inboxFolder.getFolderId());
extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId);
} else {
// Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED,
// i.e. if e.g. update not allowed on the mailbox
if (D) Log.d(TAG,"updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED");
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
}
Uri emailUri = Uri.parse(mBaseUriString);
if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString());
try {
if (D) Log.d(TAG,"updateInbox call()...");
Bundle myBundle = mProviderClient.call(
BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras);
if (myBundle != null)
return ResponseCodes.OBEX_HTTP_OK;
else {
if (D) Log.d(TAG,"updateInbox call failed");
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
}
} catch (RemoteException e){
mProviderClient = acquireUnstableContentProviderOrThrow();
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}catch (NullPointerException e) {
if(D) Log.e(TAG, "UpdateInbox - if uri or method is null", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
} catch (IllegalArgumentException e) {
if(D) Log.e(TAG, "UpdateInbox - if uri is not known", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
}
}
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
}
private BluetoothMapFolderElement getFolderElementFromName(String folderName) {
BluetoothMapFolderElement folderElement = null;
if(folderName == null || folderName.trim().isEmpty() ) {
folderElement = mCurrentFolder;
if(D) Log.d(TAG, "no folder name supplied, setting folder to current: "
+ folderElement.getName());
} else {
folderElement = mCurrentFolder.getSubFolder(folderName);
if (folderElement != null) {
if(D) Log.d(TAG, "Folder name: " + folderName +
" resulted in this element: " + folderElement.getName());
}
}
return folderElement;
}
private int pushMessage(final Operation op, String folderName,
BluetoothMapAppParams appParams, String messageVersion) {
if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " +
"appParams.getCharset() = " + appParams.getCharset());
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
InputStream bMsgStream = null;
try {
BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName);
if(folderElement == null) {
Log.w(TAG,"pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED");
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} else {
folderName = folderElement.getName();
}
if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) &&
!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) {
if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " +
"folderName=" + folderName);
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
/* - Read out the message
* - Decode into a bMessage
* - send it.
*/
BluetoothMapbMessage message;
bMsgStream = op.openInputStream();
// Decode the messageBody
message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset());
message.setVersionString(messageVersion);
// Send message
if (mObserver == null || message == null) {
// Should not happen except at shutdown.
if(D) Log.w(TAG, "mObserver or parsed message not available" );
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1))
|| ((message.getType().equals(TYPE.SMS_GSM) ||
message.getType().equals(TYPE.SMS_CDMA) ||
message.getType().equals(TYPE.MMS))
&& !folderElement.hasSmsMmsContent()) ) {
if(D) Log.w(TAG, "Wrong message type recieved" );
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString);
if (D) Log.d(TAG, "pushMessage handle: " + handle);
if (handle < 0) {
if(D) Log.w(TAG, "Message handle not created" );
return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
}
HeaderSet replyHeaders = new HeaderSet();
String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): "
+ message.getType());
replyHeaders.setHeader(HeaderSet.NAME, handleStr);
op.sendHeaders(replyHeaders);
} catch (RemoteException e) {
//reload the providerClient and return error
try {
mProviderClient = acquireUnstableContentProviderOrThrow();
}catch (RemoteException e2){
//should not happen
if(D) Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED");
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
} catch (IllegalArgumentException e) {
if (D) Log.e(TAG, "Wrongly formatted bMessage received", e);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} catch (IOException e) {
if (D) Log.e(TAG, "Exception occured: ", e);
if(mIsAborted == true) {
if(D) Log.d(TAG, "PushMessage Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (Exception e) {
if (D) Log.e(TAG, "Exception:", e);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
} finally {
if(bMsgStream != null) {
try {
bMsgStream.close();
} catch (IOException e) {}
}
}
return ResponseCodes.OBEX_HTTP_OK;
}
private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
int indicator = appParams.getStatusIndicator();
int value = appParams.getStatusValue();
String extendedData = ""; // TODO: appParams.getExtendedImData();
long handle;
BluetoothMapUtils.TYPE msgType;
if (msgHandle == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) &&
extendedData == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
if (mObserver == null) {
if(D) Log.e(TAG, "Error: no mObserver!");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
}
try {
handle = BluetoothMapUtils.getCpHandle(msgHandle);
msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
if(D) Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType);
} catch (NumberFormatException e) {
Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} catch (IllegalArgumentException e) {
Log.w(TAG, "Message type not found in handle string: " + msgHandle);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder,
mBaseUriString, value)) {
if(D) Log.w(TAG,"setMessageStatusDeleted failed");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
} else if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) {
try {
if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) {
if(D) Log.w(TAG,"not able to update the message");
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
} catch (RemoteException e) {
if(D) Log.w(TAG,"Error in setMessageStatusRead()", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
}
if (extendedData != null) {
}
return ResponseCodes.OBEX_HTTP_OK;
}
private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
throws RemoteException{
// This does only work for IM
if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
final Bundle extras = new Bundle(5);
int presenceState = appParams.getPresenceAvailability();
String presenceStatus = appParams.getPresenceStatus();
long lastActivity = appParams.getLastActivity();
int chatState = appParams.getChatState();
String chatStatusConvoId = appParams.getChatStateConvoIdString();
if(presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
presenceStatus == null &&
lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
chatStatusConvoId == null) {
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
if(presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState);
}
if (presenceStatus != null){
extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus);
}
if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER){
extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity);
}
if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER &&
chatStatusConvoId != null){
extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState);
extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId);
}
Uri uri = Uri.parse(mBaseUriString);
if (D) Log.d(TAG,"setOwnerStatus in: " + uri.toString());
try {
if (D) Log.d(TAG,"setOwnerStatus call()...");
Bundle myBundle = mProviderClient.call(
BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, extras);
if (myBundle != null)
return ResponseCodes.OBEX_HTTP_OK;
else {
if (D) Log.d(TAG,"setOwnerStatus call failed");
return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
}
} catch (RemoteException e){
mProviderClient = acquireUnstableContentProviderOrThrow();
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
} catch (NullPointerException e) {
if(D) Log.e(TAG, "setOwnerStatus - if uri or method is null", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
} catch (IllegalArgumentException e) {
if(D) Log.e(TAG, "setOwnerStatus - if uri is not known", e);
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
}
return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
}
@Override
public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
final boolean create) {
String folderName;
BluetoothMapFolderElement folder;
notifyUpdateWakeLock();
try {
folderName = (String)request.getHeader(HeaderSet.NAME);
} catch (Exception e) {
if(D) {
Log.e(TAG, "request headers error" , e);
} else {
Log.e(TAG, "request headers error");
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if (V) logHeader(request);
if (D) Log.d(TAG, "onSetPath name is " + folderName +
" backup: " + backup +
" create: " + create);
if(backup == true){
if(mCurrentFolder.getParent() != null)
mCurrentFolder = mCurrentFolder.getParent();
else
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if (folderName == null || folderName.trim().isEmpty()) {
if(backup == false)
mCurrentFolder = mCurrentFolder.getRoot();
}
else {
folder = mCurrentFolder.getSubFolder(folderName);
if(folder != null)
mCurrentFolder = folder;
else
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
return ResponseCodes.OBEX_HTTP_OK;
}
@Override
public void onClose() {
if (mCallback != null) {
Message msg = Message.obtain(mCallback);
msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
msg.arg1 = mMasId;
msg.sendToTarget();
if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
}
if(mProviderClient != null){
mProviderClient.release();
mProviderClient = null;
}
}
@Override
public int onGet(Operation op) {
notifyUpdateWakeLock();
mIsAborted = false;
HeaderSet request;
String type;
String name;
byte[] appParamRaw = null;
BluetoothMapAppParams appParams = null;
try {
request = op.getReceivedHeader();
type = (String)request.getHeader(HeaderSet.TYPE);
appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
if(appParamRaw != null)
appParams = new BluetoothMapAppParams(appParamRaw);
if (V) logHeader(request);
if (D) Log.d(TAG, "OnGet type is " + type );
if (type == null) {
if (V) Log.d(TAG, "type is null?" + type);
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
if (type.equals(TYPE_GET_FOLDER_LISTING)) {
if (V && appParams != null) {
Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = "
+ appParams.getMaxListCount()
+ ", ListStartOffset = " + appParams.getStartOffset());
}
// Block until all packets have been send.
return sendFolderListingRsp(op, appParams);
} else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
name = (String)request.getHeader(HeaderSet.NAME);
if (V && appParams != null) {
Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: folder name is: " + name +
", MaxListCount = " + appParams.getMaxListCount() +
", ListStartOffset = " + appParams.getStartOffset());
Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() +
", ParameterMask = " + appParams.getParameterMask());
Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() );
Log.d(TAG,"FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() +
", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() +
", FilterReadStatus = " + appParams.getFilterReadStatus());
Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
", FilterOriginator = " + appParams.getFilterOriginator());
Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
long tmpLong = appParams.getFilterMsgHandle();
Log.d(TAG,"FilterMsgHandle = " + (
(tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" :
Long.toHexString(tmpLong)));
SignedLongLong tmpLongLong = appParams.getFilterConvoId();
Log.d(TAG,"FilterConvoId = " + ((tmpLongLong == null) ? "" :
Long.toHexString(tmpLongLong.getLeastSignificantBits()) ) );
}
// Block until all packets have been send.
return sendMessageListingRsp(op, appParams, name);
} else if (type.equals(TYPE_GET_CONVO_LISTING)){
name = (String)request.getHeader(HeaderSet.NAME);
if (V && appParams != null) {
Log.d(TAG,"TYPE_GET_CONVO_LISTING: name is" + name +
", MaxListCount = " + appParams.getMaxListCount() +
", ListStartOffset = " + appParams.getStartOffset());
Log.d(TAG,"FilterLastActivityBegin = "+appParams.getFilterLastActivityBegin());
Log.d(TAG,"FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd());
Log.d(TAG,"FilterReadStatus = " + appParams.getFilterReadStatus());
Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient());
}
// Block until all packets have been send.
return sendConvoListingRsp(op, appParams,name);
} else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) {
if(V && appParams != null) {
Log.d(TAG,"TYPE_MESSAGE (GET): MASInstandeId = "
+ appParams.getMasInstanceId());
}
// Block until all packets have been send.
return sendMASInstanceInformationRsp(op, appParams);
} else if (type.equals(TYPE_MESSAGE)){
name = (String)request.getHeader(HeaderSet.NAME);
if(V && appParams != null) {
Log.d(TAG,"TYPE_MESSAGE (GET): name is" + name +
", Attachment = " + appParams.getAttachment() +
", Charset = " + appParams.getCharset() +
", FractionRequest = " + appParams.getFractionRequest());
}
// Block until all packets have been send.
return sendGetMessageRsp(op, name, appParams, mMessageVersion);
} else {
Log.w(TAG, "unknown type request: " + type);
return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
}
} catch (IllegalArgumentException e) {
Log.e(TAG, "Exception:", e);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} catch (ParseException e) {
Log.e(TAG, "Exception:", e);
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
} catch (Exception e) {
if(D) {
Log.e(TAG, "Exception occured while handling request",e);
} else {
Log.e(TAG, "Exception occured while handling request");
}
if(mIsAborted == true) {
if(D) Log.d(TAG, "onGet Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
}
}
/**
* Generate and send the message listing response based on an application
* parameter header. This function call will block until complete or aborted
* by the peer. Fragmentation of packets larger than the obex packet size
* will be handled by this function.
*
* @param op
* The OBEX operation.
* @param appParams
* The application parameter header
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
private int sendMessageListingRsp(Operation op,
BluetoothMapAppParams appParams,
String folderName){
OutputStream outStream = null;
byte[] outBytes = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
boolean hasUnread = false;
HeaderSet replyHeaders = new HeaderSet();
BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
BluetoothMapMessageListing outList;
if(appParams == null){
appParams = new BluetoothMapAppParams();
appParams.setMaxListCount(1024);
appParams.setStartOffset(0);
}
/* MAP Spec 1.3 introduces the following
* Messagehandle filtering:
* msgListing (messageHandle=X) -> other allowed filters: parametereMask, subjectMaxLength
* ConversationID filtering:
* msgListing (convoId empty) -> should work as normal msgListing in valid folders
* msgListing (convoId=0, no other filters) -> should return all messages in all folders
* msgListing (convoId=N, other filters) -> should return all messages in conversationID=N
* according to filters requested
*/
BluetoothMapFolderElement folderToList = null;
if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER
|| appParams.getFilterConvoId() != null) {
// If messageHandle or convoId filtering ignore folder
Log.v(TAG,"sendMessageListingRsp: ignore folder ");
folderToList = mCurrentFolder.getRoot();
folderToList.setIngore(true);
} else {
folderToList = getFolderElementFromName(folderName);
if(folderToList == null) {
Log.w(TAG,"sendMessageListingRsp: folderToList == "+
"null-sending OBEX_HTTP_BAD_REQUEST");
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
Log.v(TAG,"sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() +
"has email " + folderToList.hasEmailContent() +
"has IM " + folderToList.hasImContent() );
}
try {
// Open the OBEX body stream
outStream = op.openOutputStream();
if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
appParams.setMaxListCount(1024);
if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
appParams.setStartOffset(0);
// Check to see if we only need to send the size - hence no need to encode.
if(appParams.getMaxListCount() != 0) {
outList = mOutContent.msgListing(folderToList, appParams);
// Generate the byte stream
outAppParams.setMessageListingSize(outList.getCount());
String version;
if(0 < (mRemoteFeatureMask &
BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) {
version = BluetoothMapUtils.MAP_V11_STR;
} else {
version = BluetoothMapUtils.MAP_V10_STR;
}
/* This will only set the version, the bit must also be checked before adding any
* 1.1 bits to the listing. */
outBytes = outList.encode(mThreadIdSupport, version);
hasUnread = outList.hasUnread();
} else {
listSize = mOutContent.msgListingSize(folderToList, appParams);
hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams);
outAppParams.setMessageListingSize(listSize);
op.noBodyHeader();
}
folderToList.setIngore(false);
// Build the application parameter header
// let the peer know if there are unread messages in the list
if(hasUnread) {
outAppParams.setNewMessage(1);
}else{
outAppParams.setNewMessage(0);
}
if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT)
== BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT ) {
outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
}
if((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT)
== BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) {
// Force update of version counter if needed
mObserver.refreshFolderVersionCounter();
outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0);
}
outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
op.sendHeaders(replyHeaders);
} catch (IOException e) {
Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendMessageListingRsp Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e) {
Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException"+
" - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
if(outBytes != null) {
try {
while (bytesWritten < outBytes.length && mIsAborted == false) {
bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
outStream.write(outBytes, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
} catch (IOException e) {
if(D) Log.w(TAG,e);
// We were probably aborted or disconnected
} finally {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(bytesWritten != outBytes.length && !mIsAborted) {
Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length" +
" - sending OBEX_HTTP_BAD_REQUEST");
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} else {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
return ResponseCodes.OBEX_HTTP_OK;
}
/**
* Update the {@link BluetoothMapAppParams} object message type filter mask to only contain
* message types supported by this mas instance.
* Could the folder be used in stead?
* @param appParams Reference to the object to update
* @param overwrite True: The msgType will be overwritten to match the message types supported
* by this MAS instance. False: any unsupported message types will be masked out.
*/
private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
int masFilterMask = 0;
if(!mEnableSmsMms) {
masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS;
}
if(mAccount==null){
masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
} else {
if(!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) {
masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
}
if(!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) {
masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM;
}
}
if(overwrite) {
appParams.setFilterMessageType(masFilterMask);
} else {
int newMask = appParams.getFilterMessageType();
if(newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
appParams.setFilterMessageType(newMask);
} else {
newMask |= masFilterMask;
appParams.setFilterMessageType(newMask);
}
}
}
/**
* Generate and send the Conversation listing response based on an application
* parameter header. This function call will block until complete or aborted
* by the peer. Fragmentation of packets larger than the obex packet size
* will be handled by this function.
*
* @param op
* The OBEX operation.
* @param appParams
* The application parameter header
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
private int sendConvoListingRsp(Operation op,
BluetoothMapAppParams appParams,
String folderName){
OutputStream outStream = null;
byte[] outBytes = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
//boolean hasUnread = false;
HeaderSet replyHeaders = new HeaderSet();
BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
BluetoothMapConvoListing outList;
if(appParams == null){
appParams = new BluetoothMapAppParams();
appParams.setMaxListCount(1024);
appParams.setStartOffset(0);
}
// As the app parameters do not carry which message types to list, we set the filter here
// to all message types supported by this instance.
setMsgTypeFilterParams(appParams, true);
// Check to see if we only need to send the size - hence no need to encode.
try {
// Open the OBEX body stream
outStream = op.openOutputStream();
if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
appParams.setMaxListCount(1024);
if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
appParams.setStartOffset(0);
if(appParams.getMaxListCount() != 0) {
outList = mOutContent.convoListing(appParams, false);
outAppParams.setConvoListingSize(outList.getCount());
// Generate the byte stream
outBytes = outList.encode(); // Include thread ID for clients that supports it.
// hasUnread = outList.hasUnread();
if(D) Log.d(TAG, "outBytes size:"+ outBytes.length);
} else {
outList = mOutContent.convoListing(appParams, true);
outAppParams.setConvoListingSize(outList.getCount());
if(mEnableSmsMms) {
mOutContent.refreshSmsMmsConvoVersions();
}
if(mAccount != null) {
mOutContent.refreshImEmailConvoVersions();
}
// Force update of version counter if needed
mObserver.refreshConvoListVersionCounter();
if(0 < (mRemoteFeatureMask &
BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) {
outAppParams.setConvoListingVerCounter(
mMasInstance.getCombinedConvoListVersionCounter(), 0);
}
op.noBodyHeader();
}
if(D) Log.d(TAG, "outList size:"+ outList.getCount()
+ " MaxListCount: "+appParams.getMaxListCount());
outList = null; // We don't need it anymore - we might as well give it up for GC
outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier());
// Build the application parameter header
// The MseTime is not in the CR - but I think it is missing.
outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
op.sendHeaders(replyHeaders);
} catch (IOException e) {
Log.w(TAG,"sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendConvoListingRsp Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e) {
Log.w(TAG,"sendConvoListingRsp: IllegalArgumentException" +
" - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
if(outBytes != null) {
try {
while (bytesWritten < outBytes.length && mIsAborted == false) {
bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
outStream.write(outBytes, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
} catch (IOException e) {
if(D) Log.w(TAG,e);
// We were probably aborted or disconnected
} finally {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(bytesWritten != outBytes.length && !mIsAborted) {
Log.w(TAG,"sendConvoListingRsp: bytesWritten != outBytes.length" +
" - sending OBEX_HTTP_BAD_REQUEST");
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} else {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
return ResponseCodes.OBEX_HTTP_OK;
}
/**
* Generate and send the Folder listing response based on an application
* parameter header. This function call will block until complete or aborted
* by the peer. Fragmentation of packets larger than the obex packet size
* will be handled by this function.
*
* @param op
* The OBEX operation.
* @param appParams
* The application parameter header
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
OutputStream outStream = null;
byte[] outBytes = null;
BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
int maxChunkSize, bytesWritten = 0;
HeaderSet replyHeaders = new HeaderSet();
int bytesToWrite, maxListCount, listStartOffset;
if(appParams == null){
appParams = new BluetoothMapAppParams();
appParams.setMaxListCount(1024);
}
if(V)
Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
try {
maxListCount = appParams.getMaxListCount();
listStartOffset = appParams.getStartOffset();
if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
listStartOffset = 0;
if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
maxListCount = 1024;
if(maxListCount != 0)
{
outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
outStream = op.openOutputStream();
} else {
// ESR08 specified that this shall only be included for MaxListCount=0
outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
op.noBodyHeader();
}
// Build and set the application parameter header
replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
op.sendHeaders(replyHeaders);
} catch (IOException e1) {
Log.w(TAG,"sendFolderListingRsp: IOException" +
" - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e1) {
Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException" +
" - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
}
maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
if(outBytes != null) {
try {
while (bytesWritten < outBytes.length && mIsAborted == false) {
bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
outStream.write(outBytes, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
} catch (IOException e) {
// We were probably aborted or disconnected
} finally {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(V)
Log.v(TAG,"sendFolderList sent " + bytesWritten+" bytes out of "+ outBytes.length);
if(bytesWritten == outBytes.length || mIsAborted)
return ResponseCodes.OBEX_HTTP_OK;
else
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
return ResponseCodes.OBEX_HTTP_OK;
}
/**
* Generate and send the get MAS Instance Information response based on an MAS Instance
*
* @param op
* The OBEX operation.
* @param appParams
* The application parameter header
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams){
OutputStream outStream = null;
byte[] outBytes = null;
String outString = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
try {
if(mMasId == appParams.getMasInstanceId()) {
if(mAccount != null) {
if(mAccount.getType() == TYPE.EMAIL) {
outString = (mAccount.getName() != null) ? mAccount.getName() :
BluetoothMapMasInstance.TYPE_EMAIL_STR;
} else if(mAccount.getType() == TYPE.IM){
outString = mAccount.getUciFull();
if(outString == null) {
String uci = mAccount.getUci();
// TODO: Do we need to align this with HF/PBAP
StringBuilder sb =
new StringBuilder(uci == null ? 5 : 5 + uci.length());
sb.append("un");
if(mMasId < 10) {
sb.append("00");
} else if(mMasId < 100) {
sb.append("0");
}
sb.append(mMasId);
if(uci != null) {
sb.append(":").append(uci);
}
outString = sb.toString();
}
}
} else {
outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR;
// TODO: Add phone number if possible
}
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
/* Ensure byte array max length is 200 containing valid UTF-8 characters */
outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString,
MAS_INSTANCE_INFORMATION_LENGTH);
// Open the OBEX body stream
outStream = op.openOutputStream();
} catch (IOException e) {
Log.w(TAG,"sendMASInstanceInformationRsp: IOException" +
" - sending OBEX_HTTP_BAD_REQUEST", e);
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
}
maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
if(outBytes != null) {
try {
while (bytesWritten < outBytes.length && mIsAborted == false) {
bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
outStream.write(outBytes, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
} catch (IOException e) {
// We were probably aborted or disconnected
} finally {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(V)
Log.v(TAG,"sendMASInstanceInformationRsp sent " + bytesWritten +
" bytes out of "+ outBytes.length);
if(bytesWritten == outBytes.length || mIsAborted)
return ResponseCodes.OBEX_HTTP_OK;
else
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
return ResponseCodes.OBEX_HTTP_OK;
}
/**
* Generate and send the get message response based on an application
* parameter header and a handle.
*
* @param op
* The OBEX operation.
* @param handle
* The handle of the requested message
* @param appParams
* The application parameter header
* @param version
* The string representation of the version number(i.e. "1.0")
* @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
* {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
*/
private int sendGetMessageRsp(Operation op, String handle,
BluetoothMapAppParams appParams, String version){
OutputStream outStream = null;
byte[] outBytes = null;
int maxChunkSize, bytesToWrite, bytesWritten = 0;
try {
outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version);
outStream = op.openOutputStream();
// If it is a fraction request of Email message, set header before responding
if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)||
(BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) &&
(appParams.getFractionRequest() ==
BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) {
BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
HeaderSet replyHeaders = new HeaderSet();
outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST);
// Build and set the application parameter header
replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
outAppParams.EncodeParams());
op.sendHeaders(replyHeaders);
if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " +
"set FRACTION_DELIVER_LAST header");
}
} catch (IOException e) {
Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
if(mIsAborted == true) {
if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted");
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
} catch (IllegalArgumentException e) {
Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " +
"sending OBEX_HTTP_BAD_REQUEST", e);
if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} }
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
if(outBytes != null) {
try {
while (bytesWritten < outBytes.length && mIsAborted == false) {
bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
outStream.write(outBytes, bytesWritten, bytesToWrite);
bytesWritten += bytesToWrite;
}
} catch (IOException e) {
// We were probably aborted or disconnected
if(D && e.getMessage().equals("Abort Received")) {
Log.w(TAG, "getMessage() Aborted...", e);
}
} finally {
if(outStream != null) { try { outStream.close(); } catch (IOException e) {} }
}
if(bytesWritten == outBytes.length || mIsAborted)
return ResponseCodes.OBEX_HTTP_OK;
else
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
return ResponseCodes.OBEX_HTTP_OK;
}
@Override
public int onDelete(HeaderSet request, HeaderSet reply) {
if(D) Log.v(TAG, "onDelete() " + request.toString());
mIsAborted = false;
notifyUpdateWakeLock();
String type, name;
byte[] appParamRaw;
BluetoothMapAppParams appParams = null;
/* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */
try {
type = (String)request.getHeader(HeaderSet.TYPE);
name = (String)request.getHeader(HeaderSet.NAME);
appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
if(appParamRaw != null)
appParams = new BluetoothMapAppParams(appParamRaw);
if(D) Log.d(TAG,"type = " + type + ", name = " + name);
if(type.equals(TYPE_SET_NOTIFICATION_FILTER)) {
if(V) {
Log.d(TAG,"TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: "
+ appParams.getNotificationFilter());
}
mObserver.setNotificationFilter(appParams.getNotificationFilter());
return ResponseCodes.OBEX_HTTP_OK;
} else if (type.equals(TYPE_SET_OWNER_STATUS)) {
if(V) {
Log.d(TAG,"TYPE_SET_OWNER_STATUS:" +
" PresenceAvailability " + appParams.getPresenceAvailability() +
", PresenceStatus: " + appParams.getPresenceStatus() +
", LastActivity: " + appParams.getLastActivityString() +
", ChatStatus: " + appParams.getChatState() +
", ChatStatusConvoId: " + appParams.getChatStateConvoIdString());
}
return setOwnerStatus(name, appParams);
}
} catch (RemoteException e){
//reload the providerClient and return error
try {
mProviderClient = acquireUnstableContentProviderOrThrow();
}catch (RemoteException e2){
//should not happen
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}catch (Exception e) {
if(D) {
Log.e(TAG, "Exception occured while handling request",e);
} else {
Log.e(TAG, "Exception occured while handling request");
}
if(mIsAborted) {
return ResponseCodes.OBEX_HTTP_OK;
} else {
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
}
return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
}
private void notifyUpdateWakeLock() {
if(mCallback != null) {
Message msg = Message.obtain(mCallback);
msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
msg.sendToTarget();
}
}
private static final void logHeader(HeaderSet hs) {
Log.v(TAG, "Dumping HeaderSet " + hs.toString());
try {
Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
} catch (IOException e) {
Log.e(TAG, "dump HeaderSet error " + e);
}
Log.v(TAG, "NEW!!! Dumping HeaderSet END");
}
}