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