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