/* * 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 com.android.bluetooth.mapapi.BluetoothMapEmailContract; 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 = Log.isLoggable(BluetoothMapService.LOG_TAG, Log.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(); } if (account != null && account.getType() == TYPE.EMAIL) { mOutContent = new BluetoothMapContentEmail(mContext, mAccount, mMasInstance); } else { mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance); } buildFolderStructure(); /* Build the default folder structure, and set mCurrentFolder to root folder */ mObserver.setFolderStructure(mCurrentFolder.getRoot()); } /** * */ 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 && (mOutContent instanceof BluetoothMapContentEmail)) { if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString()); ((BluetoothMapContentEmail)mOutContent).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:"); } if (mAccount!= null && mAccount.getType() == TYPE.EMAIL && (mOutContent instanceof BluetoothMapContentEmail)) { ((BluetoothMapContentEmail)mOutContent).msgUpdate(); return ResponseCodes.OBEX_HTTP_OK; } 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) && !folderName.equalsIgnoreCase(BluetoothMapEmailContract.FOLDER_NAME_DRAFTS)) { 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); if(D) Log.d(TAG, "pushMessage: charset" + appParams.getCharset()+ "folderId: " + folderElement.getFolderId() + "Name: " + folderName +"TYPE: " + message.getType()); // 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 (D) Log.d(TAG, "setMessageStatus():"); 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 { if (mAccount!= null && mAccount.getType() == TYPE.EMAIL) { if (folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) { folderName = BluetoothMapEmailContract.FOLDER_NAME_DRAFTS; } } folder = mCurrentFolder.getSubFolder(folderName); if(folder != null) { mCurrentFolder = folder; } else { Log.w(TAG,"SubFolder Not found! : " + mCurrentFolder.getName()); 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"); } }