Home | History | Annotate | Download | only in map
      1 /*
      2 * Copyright (C) 2013 Samsung System LSI
      3 * Licensed under the Apache License, Version 2.0 (the "License");
      4 * you may not use this file except in compliance with the License.
      5 * You may obtain a copy of the License at
      6 *
      7 *      http://www.apache.org/licenses/LICENSE-2.0
      8 *
      9 * Unless required by applicable law or agreed to in writing, software
     10 * distributed under the License is distributed on an "AS IS" BASIS,
     11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 * See the License for the specific language governing permissions and
     13 * limitations under the License.
     14 */
     15 package com.android.bluetooth.map;
     16 
     17 import java.io.IOException;
     18 import java.io.InputStream;
     19 import java.io.OutputStream;
     20 import java.util.Arrays;
     21 import java.util.Calendar;
     22 
     23 import javax.obex.HeaderSet;
     24 import javax.obex.Operation;
     25 import javax.obex.ResponseCodes;
     26 import javax.obex.ServerRequestHandler;
     27 
     28 import com.android.bluetooth.map.BluetoothMapUtils;
     29 import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
     30 
     31 import android.content.Context;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 import android.util.Log;
     35 
     36 public class BluetoothMapObexServer extends ServerRequestHandler {
     37 
     38     private static final String TAG = "BluetoothMapObexServer";
     39 
     40     private static final boolean D = BluetoothMapService.DEBUG;
     41     private static final boolean V = BluetoothMapService.VERBOSE;
     42 
     43     private static final int UUID_LENGTH = 16;
     44 
     45     // 128 bit UUID for MAP
     46     private static final byte[] MAP_TARGET = new byte[] {
     47              (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40,
     48              (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB,
     49              (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00,
     50              (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66
     51              };
     52 
     53     /* Message types */
     54     private static final String TYPE_GET_FOLDER_LISTING  = "x-obex/folder-listing";
     55     private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
     56     private static final String TYPE_MESSAGE             = "x-bt/message";
     57     private static final String TYPE_SET_MESSAGE_STATUS  = "x-bt/messageStatus";
     58     private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration";
     59     private static final String TYPE_MESSAGE_UPDATE      = "x-bt/MAP-messageUpdate";
     60 
     61     private BluetoothMapFolderElement mCurrentFolder;
     62 
     63     private BluetoothMnsObexClient mMnsClient;
     64 
     65     private Handler mCallback = null;
     66 
     67     private Context mContext;
     68 
     69     public static boolean sIsAborted = false;
     70 
     71     BluetoothMapContent mOutContent;
     72 
     73     public BluetoothMapObexServer(Handler callback, Context context,
     74                                   BluetoothMnsObexClient mns) {
     75         super();
     76         mCallback = callback;
     77         mContext = context;
     78         mOutContent = new BluetoothMapContent(mContext);
     79         mMnsClient = mns;
     80         buildFolderStructure(); /* Build the default folder structure, and set
     81                                    mCurrentFolder to root folder */
     82     }
     83 
     84     /**
     85      * Build the default minimal folder structure, as defined in the MAP specification.
     86      */
     87     private void buildFolderStructure(){
     88         mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element
     89         BluetoothMapFolderElement tmpFolder;
     90         tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom
     91         tmpFolder = tmpFolder.addFolder("msg");          // root/telecom/msg
     92         tmpFolder.addFolder("inbox");                    // root/telecom/msg/inbox
     93         tmpFolder.addFolder("outbox");
     94         tmpFolder.addFolder("sent");
     95         tmpFolder.addFolder("deleted");
     96         tmpFolder.addFolder("draft");
     97     }
     98 
     99     @Override
    100     public int onConnect(final HeaderSet request, HeaderSet reply) {
    101         if (D) Log.d(TAG, "onConnect():");
    102         if (V) logHeader(request);
    103         try {
    104             byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET);
    105             if (uuid == null) {
    106                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    107             }
    108             if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid));
    109 
    110             if (uuid.length != UUID_LENGTH) {
    111                 Log.w(TAG, "Wrong UUID length");
    112                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    113             }
    114             for (int i = 0; i < UUID_LENGTH; i++) {
    115                 if (uuid[i] != MAP_TARGET[i]) {
    116                     Log.w(TAG, "Wrong UUID");
    117                     return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    118                 }
    119             }
    120             reply.setHeader(HeaderSet.WHO, uuid);
    121         } catch (IOException e) {
    122             Log.e(TAG, e.toString());
    123             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    124         }
    125 
    126         try {
    127             byte[] remote = (byte[])request.getHeader(HeaderSet.WHO);
    128             if (remote != null) {
    129                 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote));
    130                 reply.setHeader(HeaderSet.TARGET, remote);
    131             }
    132         } catch (IOException e) {
    133             Log.e(TAG, e.toString());
    134             return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
    135         }
    136 
    137         if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " +
    138                 "MSG_SESSION_ESTABLISHED msg.");
    139 
    140 
    141         Message msg = Message.obtain(mCallback);
    142         msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED;
    143         msg.sendToTarget();
    144 
    145         return ResponseCodes.OBEX_HTTP_OK;
    146     }
    147 
    148     @Override
    149     public void onDisconnect(final HeaderSet req, final HeaderSet resp) {
    150         if (D) Log.d(TAG, "onDisconnect(): enter");
    151         if (V) logHeader(req);
    152 
    153         resp.responseCode = ResponseCodes.OBEX_HTTP_OK;
    154         if (mCallback != null) {
    155             Message msg = Message.obtain(mCallback);
    156             msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED;
    157             msg.sendToTarget();
    158             if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out.");
    159         }
    160     }
    161 
    162     @Override
    163     public int onAbort(HeaderSet request, HeaderSet reply) {
    164         if (D) Log.d(TAG, "onAbort(): enter.");
    165         sIsAborted = true;
    166         return ResponseCodes.OBEX_HTTP_OK;
    167     }
    168 
    169     @Override
    170     public int onPut(final Operation op) {
    171         if (D) Log.d(TAG, "onPut(): enter");
    172         HeaderSet request = null;
    173         String type, name;
    174         byte[] appParamRaw;
    175         BluetoothMapAppParams appParams = null;
    176 
    177         try {
    178             request = op.getReceivedHeader();
    179             type = (String)request.getHeader(HeaderSet.TYPE);
    180             name = (String)request.getHeader(HeaderSet.NAME);
    181             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    182             if(appParamRaw != null)
    183                 appParams = new BluetoothMapAppParams(appParamRaw);
    184         } catch (Exception e) {
    185             Log.e(TAG, "request headers error");
    186             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    187         }
    188 
    189         if(D) Log.d(TAG,"type = " + type + ", name = " + name);
    190         if (type.equals(TYPE_MESSAGE_UPDATE)) {
    191             if(V) {
    192                 Log.d(TAG,"TYPE_MESSAGE_UPDATE:");
    193             }
    194             return ResponseCodes.OBEX_HTTP_OK;
    195         }else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) {
    196             if(V) {
    197                 Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " + appParams.getNotificationStatus());
    198             }
    199             return setNotificationRegistration(appParams);
    200         }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) {
    201             if(V) {
    202                 Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: " + appParams.getStatusIndicator() + ", StatusValue: " + appParams.getStatusValue());
    203             }
    204             return setMessageStatus(name, appParams);
    205         } else if (type.equals(TYPE_MESSAGE)) {
    206             if(V) {
    207                 Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent() +  ", Retry: " + appParams.getRetry());
    208                 Log.d(TAG,"              charset: " + appParams.getCharset());
    209             }
    210             return pushMessage(op, name, appParams);
    211 
    212         }
    213 
    214         return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    215     }
    216 
    217     private int setNotificationRegistration(BluetoothMapAppParams appParams) {
    218         // Forward the request to the MNS thread as a message - including the MAS instance ID.
    219         Handler mns = mMnsClient.getMessageHandler();
    220         if(mns != null) {
    221             Message msg = Message.obtain(mns);
    222             msg.what = BluetoothMnsObexClient.MSG_MNS_NOTIFICATION_REGISTRATION;
    223             msg.arg1 = 0; // TODO: Add correct MAS ID, as specified in the SDP record.
    224             msg.arg2 = appParams.getNotificationStatus();
    225             msg.sendToTarget();
    226             if(D) Log.d(TAG,"MSG_MNS_NOTIFICATION_REGISTRATION");
    227             return ResponseCodes.OBEX_HTTP_OK;
    228         } else {
    229             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // This should not happen.
    230         }
    231     }
    232 
    233     private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) {
    234         if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) {
    235             if(D) Log.d(TAG, "Missing charset - unable to decode message content. appParams.getCharset() = " + appParams.getCharset());
    236             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    237         }
    238         try {
    239             if(folderName == null || folderName.equals("")) {
    240                 folderName = mCurrentFolder.getName();
    241             }
    242             if(!folderName.equals("outbox") && !folderName.equals("draft")) {
    243                 if(D) Log.d(TAG, "Push message only allowed to outbox and draft. folderName: " + folderName);
    244                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    245             }
    246             /*  - Read out the message
    247              *  - Decode into a bMessage
    248              *  - send it.
    249              */
    250             InputStream bMsgStream;
    251             BluetoothMapbMessage message;
    252             bMsgStream = op.openInputStream();
    253             message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); // Decode the messageBody
    254             // Send message
    255             BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
    256             if (observer == null) {
    257                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    258             }
    259 
    260             long handle = observer.pushMessage(message, folderName, appParams);
    261             if (D) Log.d(TAG, "pushMessage handle: " + handle);
    262             if (handle < 0) {
    263                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    264             }
    265             HeaderSet replyHeaders = new HeaderSet();
    266             String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
    267             if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
    268             replyHeaders.setHeader(HeaderSet.NAME, handleStr);
    269             op.sendHeaders(replyHeaders);
    270 
    271             bMsgStream.close();
    272         } catch (IllegalArgumentException e) {
    273             if(D) Log.w(TAG, "Wrongly formatted bMessage received", e);
    274             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    275         } catch (Exception e) {
    276             // TODO: Change to IOException after debug
    277             Log.e(TAG, "Exception occured: ", e);
    278             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    279         }
    280         return ResponseCodes.OBEX_HTTP_OK;
    281     }
    282 
    283     private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
    284         int indicator = appParams.getStatusIndicator();
    285         int value = appParams.getStatusValue();
    286         long handle;
    287         BluetoothMapUtils.TYPE msgType;
    288 
    289         if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
    290            value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
    291            msgHandle == null) {
    292             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    293         }
    294         BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
    295         if (observer == null) {
    296             return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
    297         }
    298 
    299         try {
    300             handle = BluetoothMapUtils.getCpHandle(msgHandle);
    301             msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
    302         } catch (NumberFormatException e) {
    303             Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
    304             return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
    305         }
    306 
    307         if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
    308             if (!observer.setMessageStatusDeleted(handle, msgType, value)) {
    309                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    310             }
    311         } else /* BluetoothMapAppParams.STATUS_INDICATOR_READE */ {
    312             if (!observer.setMessageStatusRead(handle, msgType, value)) {
    313                 return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
    314             }
    315         }
    316         return ResponseCodes.OBEX_HTTP_OK;
    317     }
    318 
    319     @Override
    320     public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
    321             final boolean create) {
    322         String folderName;
    323         BluetoothMapFolderElement folder;
    324         try {
    325             folderName = (String)request.getHeader(HeaderSet.NAME);
    326         } catch (Exception e) {
    327             Log.e(TAG, "request headers error");
    328             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    329         }
    330 
    331         if (V) logHeader(request);
    332         if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
    333                      + "create: " + create);
    334 
    335         if(backup == true){
    336             if(mCurrentFolder.getParent() != null)
    337                 mCurrentFolder = mCurrentFolder.getParent();
    338             else
    339                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    340         }
    341 
    342         if (folderName == null || folderName == "") {
    343             if(backup == false)
    344                 mCurrentFolder = mCurrentFolder.getRoot();
    345         }
    346         else {
    347             folder = mCurrentFolder.getSubFolder(folderName);
    348             if(folder != null)
    349                 mCurrentFolder = folder;
    350             else
    351                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    352         }
    353         if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
    354         return ResponseCodes.OBEX_HTTP_OK;
    355     }
    356 
    357     @Override
    358     public void onClose() {
    359         if (mCallback != null) {
    360             Message msg = Message.obtain(mCallback);
    361             msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
    362             msg.sendToTarget();
    363             if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
    364         }
    365     }
    366 
    367     @Override
    368     public int onGet(Operation op) {
    369         sIsAborted = false;
    370         HeaderSet request;
    371         String type;
    372         String name;
    373         byte[] appParamRaw = null;
    374         BluetoothMapAppParams appParams = null;
    375         try {
    376             request = op.getReceivedHeader();
    377             type = (String)request.getHeader(HeaderSet.TYPE);
    378             name = (String)request.getHeader(HeaderSet.NAME);
    379             appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
    380             if(appParamRaw != null)
    381                 appParams = new BluetoothMapAppParams(appParamRaw);
    382 
    383             if (V) logHeader(request);
    384             if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name);
    385 
    386             if (type == null) {
    387                 if (V) Log.d(TAG, "type is null?" + type);
    388                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    389             }
    390 
    391             if (type.equals(TYPE_GET_FOLDER_LISTING)) {
    392                 if (V && appParams != null) {
    393                     Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
    394                               ", ListStartOffset = " + appParams.getStartOffset());
    395                 }
    396                 return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
    397             }
    398             else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
    399                 if (V && appParams != null) {
    400                     Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() +
    401                               ", ListStartOffset = " + appParams.getStartOffset());
    402                     Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " +
    403                               appParams.getParameterMask());
    404                     Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() +
    405                               ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin());
    406                     Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() +
    407                               ", FilterReadStatus = " + appParams.getFilterReadStatus());
    408                     Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
    409                               ", FilterOriginator = " + appParams.getFilterOriginator());
    410                     Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
    411                 }
    412                 return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
    413             }
    414             else if (type.equals(TYPE_MESSAGE)){
    415                 if(V && appParams != null) {
    416                     Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + ", Charset = " + appParams.getCharset() +
    417                         ", FractionRequest = " + appParams.getFractionRequest());
    418                 }
    419                 return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
    420             }
    421             else {
    422                 Log.w(TAG, "unknown type request: " + type);
    423                 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
    424             }
    425         } catch (Exception e) {
    426             // TODO: Move to the part that actually throws exceptions, and change to the correat exception type
    427             Log.e(TAG, "request headers error, Exception:", e);
    428             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    429         }
    430     }
    431 
    432     /**
    433      * Generate and send the message listing response based on an application
    434      * parameter header. This function call will block until complete or aborted
    435      * by the peer. Fragmentation of packets larger than the obex packet size
    436      * will be handled by this function.
    437      *
    438      * @param op
    439      *            The OBEX operation.
    440      * @param appParams
    441      *            The application parameter header
    442      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    443      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    444      */
    445     private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
    446         OutputStream outStream = null;
    447         byte[] outBytes = null;
    448         int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
    449         boolean hasUnread = false;
    450         HeaderSet replyHeaders = new HeaderSet();
    451         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
    452         BluetoothMapMessageListing outList;
    453         if(folderName == null) {
    454             folderName = mCurrentFolder.getName();
    455         }
    456         if(appParams == null){
    457             appParams = new BluetoothMapAppParams();
    458             appParams.setMaxListCount(1024);
    459             appParams.setStartOffset(0);
    460         }
    461 
    462         // Check to see if we only need to send the size - hence no need to encode.
    463         try {
    464             // Open the OBEX body stream
    465             outStream = op.openOutputStream();
    466 
    467             if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    468                 appParams.setMaxListCount(1024);
    469 
    470             if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    471                 appParams.setStartOffset(0);
    472 
    473             if(appParams.getMaxListCount() != 0) {
    474                 outList = mOutContent.msgListing(folderName, appParams);
    475                 // Generate the byte stream
    476                 outAppParams.setMessageListingSize(outList.getCount());
    477                 outBytes = outList.encode();
    478                 hasUnread = outList.hasUnread();
    479             }
    480             else {
    481                 listSize = mOutContent.msgListingSize(folderName, appParams);
    482                 hasUnread = mOutContent.msgListingHasUnread(folderName, appParams);
    483                 outAppParams.setMessageListingSize(listSize);
    484                 op.noBodyHeader();
    485             }
    486 
    487             // Build the application parameter header
    488 
    489             // let the peer know if there are unread messages in the list
    490             if(hasUnread)
    491             {
    492                 outAppParams.setNewMessage(1);
    493             }else{
    494                 outAppParams.setNewMessage(0);
    495             }
    496 
    497             outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
    498             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
    499             op.sendHeaders(replyHeaders);
    500 
    501         } catch (IOException e) {
    502             Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
    503             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    504         } catch (IllegalArgumentException e) {
    505             Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
    506             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    507         }
    508 
    509         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    510         if(outBytes != null) {
    511             try {
    512                 while (bytesWritten < outBytes.length && sIsAborted == false) {
    513                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    514                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    515                     bytesWritten += bytesToWrite;
    516                 }
    517             } catch (IOException e) {
    518                 if(V) Log.w(TAG,e);
    519                 // We were probably aborted or disconnected
    520             } finally {
    521                 if(outStream != null) {
    522                     try {
    523                         outStream.close();
    524                     } catch (IOException e) {
    525                         // If an error occurs during close, there is no more cleanup to do
    526                     }
    527                 }
    528             }
    529             if(bytesWritten != outBytes.length)
    530                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    531         } else {
    532             try {
    533                 outStream.close();
    534             } catch (IOException e) {
    535                 // If an error occurs during close, there is no more cleanup to do
    536             }
    537         }
    538         return ResponseCodes.OBEX_HTTP_OK;
    539     }
    540 
    541     /**
    542      * Generate and send the Folder listing response based on an application
    543      * parameter header. This function call will block until complete or aborted
    544      * by the peer. Fragmentation of packets larger than the obex packet size
    545      * will be handled by this function.
    546      *
    547      * @param op
    548      *            The OBEX operation.
    549      * @param appParams
    550      *            The application parameter header
    551      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    552      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    553      */
    554     private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
    555         OutputStream outStream = null;
    556         byte[] outBytes = null;
    557         BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
    558         int maxChunkSize, bytesWritten = 0;
    559         HeaderSet replyHeaders = new HeaderSet();
    560         int bytesToWrite, maxListCount, listStartOffset;
    561         if(appParams == null){
    562             appParams = new BluetoothMapAppParams();
    563             appParams.setMaxListCount(1024);
    564         }
    565 
    566         if(V)
    567             Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
    568 
    569         try {
    570             maxListCount = appParams.getMaxListCount();
    571             listStartOffset = appParams.getStartOffset();
    572 
    573             if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    574                 listStartOffset = 0;
    575 
    576             if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
    577                 maxListCount = 1024;
    578 
    579             if(maxListCount != 0)
    580             {
    581                 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
    582                 outStream = op.openOutputStream();
    583             }
    584 
    585             // Build and set the application parameter header
    586             outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
    587             replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
    588             op.sendHeaders(replyHeaders);
    589 
    590         } catch (IOException e1) {
    591             Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
    592             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    593         } catch (IllegalArgumentException e1) {
    594             Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
    595             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    596         }
    597 
    598         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    599 
    600         if(outBytes != null) {
    601             try {
    602                 while (bytesWritten < outBytes.length && sIsAborted == false) {
    603                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    604                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    605                     bytesWritten += bytesToWrite;
    606                 }
    607             } catch (IOException e) {
    608                 // We were probably aborted or disconnected
    609             } finally {
    610                 if(outStream != null) {
    611                     try {
    612                         outStream.close();
    613                     } catch (IOException e) {
    614                         // If an error occurs during close, there is no more cleanup to do
    615                     }
    616                 }
    617             }
    618             if(V)
    619                 Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
    620             if(bytesWritten == outBytes.length)
    621                 return ResponseCodes.OBEX_HTTP_OK;
    622             else
    623                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    624         }
    625 
    626         return ResponseCodes.OBEX_HTTP_OK;
    627     }
    628 
    629     /**
    630      * Generate and send the get message response based on an application
    631      * parameter header and a handle.
    632      *
    633      * @param op
    634      *            The OBEX operation.
    635      * @param appParams
    636      *            The application parameter header
    637      * @param handle
    638      *            The handle of the requested message
    639      * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
    640      *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
    641      */
    642     private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){
    643         OutputStream outStream ;
    644         byte[] outBytes;
    645         int maxChunkSize, bytesToWrite, bytesWritten = 0;
    646         long msgHandle;
    647 
    648         try {
    649             outBytes = mOutContent.getMessage(handle, appParams);
    650             outStream = op.openOutputStream();
    651 
    652         } catch (IOException e) {
    653             Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
    654             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    655         } catch (IllegalArgumentException e) {
    656             Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - sending OBEX_HTTP_BAD_REQUEST", e);
    657             return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    658         }
    659 
    660         maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
    661 
    662         if(outBytes != null) {
    663             try {
    664                 while (bytesWritten < outBytes.length && sIsAborted == false) {
    665                     bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
    666                     outStream.write(outBytes, bytesWritten, bytesToWrite);
    667                     bytesWritten += bytesToWrite;
    668                 }
    669             } catch (IOException e) {
    670                 // We were probably aborted or disconnected
    671             } finally {
    672                 if(outStream != null) {
    673                     try {
    674                         outStream.close();
    675                     } catch (IOException e) {
    676                         // If an error occurs during close, there is no more cleanup to do
    677                     }
    678                 }
    679             }
    680             if(bytesWritten == outBytes.length)
    681                 return ResponseCodes.OBEX_HTTP_OK;
    682             else
    683                 return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
    684         }
    685 
    686         return ResponseCodes.OBEX_HTTP_OK;
    687     }
    688 
    689 
    690     private static final void logHeader(HeaderSet hs) {
    691         Log.v(TAG, "Dumping HeaderSet " + hs.toString());
    692         try {
    693             Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
    694             Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
    695             Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
    696             Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
    697             Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
    698             Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
    699         } catch (IOException e) {
    700             Log.e(TAG, "dump HeaderSet error " + e);
    701         }
    702         Log.v(TAG, "NEW!!! Dumping HeaderSet END");
    703     }
    704 }
    705