BluetoothMapObexServer.java revision a871a846ce37339a05fdf64b26bda0dc5bb6d565
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 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            folderName = folderName.toLowerCase();
243            if(!folderName.equals("outbox") && !folderName.equals("draft")) {
244                if(D) Log.d(TAG, "Push message only allowed to outbox and draft. folderName: " + folderName);
245                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
246            }
247            /*  - Read out the message
248             *  - Decode into a bMessage
249             *  - send it.
250             */
251            InputStream bMsgStream;
252            BluetoothMapbMessage message;
253            bMsgStream = op.openInputStream();
254            message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); // Decode the messageBody
255            // Send message
256            BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
257            if (observer == null) {
258                return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
259            }
260
261            long handle = observer.pushMessage(message, folderName, appParams);
262            if (D) Log.d(TAG, "pushMessage handle: " + handle);
263            if (handle < 0) {
264                return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
265            }
266            HeaderSet replyHeaders = new HeaderSet();
267            String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType());
268            if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType());
269            replyHeaders.setHeader(HeaderSet.NAME, handleStr);
270            op.sendHeaders(replyHeaders);
271
272            bMsgStream.close();
273        } catch (IllegalArgumentException e) {
274            if(D) Log.w(TAG, "Wrongly formatted bMessage received", e);
275            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
276        } catch (Exception e) {
277            // TODO: Change to IOException after debug
278            Log.e(TAG, "Exception occured: ", e);
279            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
280        }
281        return ResponseCodes.OBEX_HTTP_OK;
282    }
283
284    private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) {
285        int indicator = appParams.getStatusIndicator();
286        int value = appParams.getStatusValue();
287        long handle;
288        BluetoothMapUtils.TYPE msgType;
289
290        if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
291           value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER ||
292           msgHandle == null) {
293            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
294        }
295        BluetoothMapContentObserver observer = mMnsClient.getContentObserver();
296        if (observer == null) {
297            return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen.
298        }
299
300        try {
301            handle = BluetoothMapUtils.getCpHandle(msgHandle);
302            msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle);
303        } catch (NumberFormatException e) {
304            Log.w(TAG, "Wrongly formatted message handle: " + msgHandle);
305            return ResponseCodes.OBEX_HTTP_PRECON_FAILED;
306        }
307
308        if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) {
309            if (!observer.setMessageStatusDeleted(handle, msgType, value)) {
310                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
311            }
312        } else /* BluetoothMapAppParams.STATUS_INDICATOR_READE */ {
313            if (!observer.setMessageStatusRead(handle, msgType, value)) {
314                return ResponseCodes.OBEX_HTTP_UNAVAILABLE;
315            }
316        }
317        return ResponseCodes.OBEX_HTTP_OK;
318    }
319
320    @Override
321    public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup,
322            final boolean create) {
323        String folderName;
324        BluetoothMapFolderElement folder;
325        try {
326            folderName = (String)request.getHeader(HeaderSet.NAME);
327        } catch (Exception e) {
328            Log.e(TAG, "request headers error");
329            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
330        }
331
332        if (V) logHeader(request);
333        if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup
334                     + "create: " + create);
335
336        if(backup == true){
337            if(mCurrentFolder.getParent() != null)
338                mCurrentFolder = mCurrentFolder.getParent();
339            else
340                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
341        }
342
343        if (folderName == null || folderName == "") {
344            if(backup == false)
345                mCurrentFolder = mCurrentFolder.getRoot();
346        }
347        else {
348            folder = mCurrentFolder.getSubFolder(folderName);
349            if(folder != null)
350                mCurrentFolder = folder;
351            else
352                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
353        }
354        if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName());
355        return ResponseCodes.OBEX_HTTP_OK;
356    }
357
358    @Override
359    public void onClose() {
360        if (mCallback != null) {
361            Message msg = Message.obtain(mCallback);
362            msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE;
363            msg.sendToTarget();
364            if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out.");
365        }
366    }
367
368    @Override
369    public int onGet(Operation op) {
370        sIsAborted = false;
371        HeaderSet request;
372        String type;
373        String name;
374        byte[] appParamRaw = null;
375        BluetoothMapAppParams appParams = null;
376        try {
377            request = op.getReceivedHeader();
378            type = (String)request.getHeader(HeaderSet.TYPE);
379            name = (String)request.getHeader(HeaderSet.NAME);
380            appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER);
381            if(appParamRaw != null)
382                appParams = new BluetoothMapAppParams(appParamRaw);
383
384            if (V) logHeader(request);
385            if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name);
386
387            if (type == null) {
388                if (V) Log.d(TAG, "type is null?" + type);
389                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
390            }
391
392            if (type.equals(TYPE_GET_FOLDER_LISTING)) {
393                if (V && appParams != null) {
394                    Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() +
395                              ", ListStartOffset = " + appParams.getStartOffset());
396                }
397                return sendFolderListingRsp(op, appParams); // Block until all packets have been send.
398            }
399            else if (type.equals(TYPE_GET_MESSAGE_LISTING)){
400                if (V && appParams != null) {
401                    Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() +
402                              ", ListStartOffset = " + appParams.getStartOffset());
403                    Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " +
404                              appParams.getParameterMask());
405                    Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() +
406                              ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin());
407                    Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() +
408                              ", FilterReadStatus = " + appParams.getFilterReadStatus());
409                    Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() +
410                              ", FilterOriginator = " + appParams.getFilterOriginator());
411                    Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority());
412                }
413                return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send.
414            }
415            else if (type.equals(TYPE_MESSAGE)){
416                if(V && appParams != null) {
417                    Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + ", Charset = " + appParams.getCharset() +
418                        ", FractionRequest = " + appParams.getFractionRequest());
419                }
420                return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send.
421            }
422            else {
423                Log.w(TAG, "unknown type request: " + type);
424                return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE;
425            }
426        } catch (Exception e) {
427            // TODO: Move to the part that actually throws exceptions, and change to the correat exception type
428            Log.e(TAG, "request headers error, Exception:", e);
429            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
430        }
431    }
432
433    /**
434     * Generate and send the message listing response based on an application
435     * parameter header. This function call will block until complete or aborted
436     * by the peer. Fragmentation of packets larger than the obex packet size
437     * will be handled by this function.
438     *
439     * @param op
440     *            The OBEX operation.
441     * @param appParams
442     *            The application parameter header
443     * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
444     *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
445     */
446    private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){
447        OutputStream outStream = null;
448        byte[] outBytes = null;
449        int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize;
450        boolean hasUnread = false;
451        HeaderSet replyHeaders = new HeaderSet();
452        BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
453        BluetoothMapMessageListing outList;
454        if(folderName == null) {
455            folderName = mCurrentFolder.getName();
456        }
457        if(appParams == null){
458            appParams = new BluetoothMapAppParams();
459            appParams.setMaxListCount(1024);
460            appParams.setStartOffset(0);
461        }
462
463        // Check to see if we only need to send the size - hence no need to encode.
464        try {
465            // Open the OBEX body stream
466            outStream = op.openOutputStream();
467
468            if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
469                appParams.setMaxListCount(1024);
470
471            if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
472                appParams.setStartOffset(0);
473
474            if(appParams.getMaxListCount() != 0) {
475                outList = mOutContent.msgListing(folderName, appParams);
476                // Generate the byte stream
477                outAppParams.setMessageListingSize(outList.getCount());
478                outBytes = outList.encode();
479                hasUnread = outList.hasUnread();
480            }
481            else {
482                listSize = mOutContent.msgListingSize(folderName, appParams);
483                hasUnread = mOutContent.msgListingHasUnread(folderName, appParams);
484                outAppParams.setMessageListingSize(listSize);
485                op.noBodyHeader();
486            }
487
488            // Build the application parameter header
489
490            // let the peer know if there are unread messages in the list
491            if(hasUnread)
492            {
493                outAppParams.setNewMessage(1);
494            }else{
495                outAppParams.setNewMessage(0);
496            }
497
498            outAppParams.setMseTime(Calendar.getInstance().getTime().getTime());
499            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
500            op.sendHeaders(replyHeaders);
501
502        } catch (IOException e) {
503            Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
504            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
505        } catch (IllegalArgumentException e) {
506            Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e);
507            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
508        }
509
510        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
511        if(outBytes != null) {
512            try {
513                while (bytesWritten < outBytes.length && sIsAborted == false) {
514                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
515                    outStream.write(outBytes, bytesWritten, bytesToWrite);
516                    bytesWritten += bytesToWrite;
517                }
518            } catch (IOException e) {
519                if(V) Log.w(TAG,e);
520                // We were probably aborted or disconnected
521            } finally {
522                if(outStream != null) {
523                    try {
524                        outStream.close();
525                    } catch (IOException e) {
526                        // If an error occurs during close, there is no more cleanup to do
527                    }
528                }
529            }
530            if(bytesWritten != outBytes.length)
531                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
532        } else {
533            try {
534                outStream.close();
535            } catch (IOException e) {
536                // If an error occurs during close, there is no more cleanup to do
537            }
538        }
539        return ResponseCodes.OBEX_HTTP_OK;
540    }
541
542    /**
543     * Generate and send the Folder listing response based on an application
544     * parameter header. This function call will block until complete or aborted
545     * by the peer. Fragmentation of packets larger than the obex packet size
546     * will be handled by this function.
547     *
548     * @param op
549     *            The OBEX operation.
550     * @param appParams
551     *            The application parameter header
552     * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
553     *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
554     */
555    private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){
556        OutputStream outStream = null;
557        byte[] outBytes = null;
558        BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
559        int maxChunkSize, bytesWritten = 0;
560        HeaderSet replyHeaders = new HeaderSet();
561        int bytesToWrite, maxListCount, listStartOffset;
562        if(appParams == null){
563            appParams = new BluetoothMapAppParams();
564            appParams.setMaxListCount(1024);
565        }
566
567        if(V)
568            Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName());
569
570        try {
571            maxListCount = appParams.getMaxListCount();
572            listStartOffset = appParams.getStartOffset();
573
574            if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
575                listStartOffset = 0;
576
577            if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER)
578                maxListCount = 1024;
579
580            if(maxListCount != 0)
581            {
582                outBytes = mCurrentFolder.encode(listStartOffset, maxListCount);
583                outStream = op.openOutputStream();
584            }
585
586            // Build and set the application parameter header
587            outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount());
588            replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams());
589            op.sendHeaders(replyHeaders);
590
591        } catch (IOException e1) {
592            Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
593            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
594        } catch (IllegalArgumentException e1) {
595            Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1);
596            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
597        }
598
599        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
600
601        if(outBytes != null) {
602            try {
603                while (bytesWritten < outBytes.length && sIsAborted == false) {
604                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
605                    outStream.write(outBytes, bytesWritten, bytesToWrite);
606                    bytesWritten += bytesToWrite;
607                }
608            } catch (IOException e) {
609                // We were probably aborted or disconnected
610            } finally {
611                if(outStream != null) {
612                    try {
613                        outStream.close();
614                    } catch (IOException e) {
615                        // If an error occurs during close, there is no more cleanup to do
616                    }
617                }
618            }
619            if(V)
620                Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length);
621            if(bytesWritten == outBytes.length)
622                return ResponseCodes.OBEX_HTTP_OK;
623            else
624                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
625        }
626
627        return ResponseCodes.OBEX_HTTP_OK;
628    }
629
630    /**
631     * Generate and send the get message response based on an application
632     * parameter header and a handle.
633     *
634     * @param op
635     *            The OBEX operation.
636     * @param appParams
637     *            The application parameter header
638     * @param handle
639     *            The handle of the requested message
640     * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or
641     *         {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error.
642     */
643    private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){
644        OutputStream outStream ;
645        byte[] outBytes;
646        int maxChunkSize, bytesToWrite, bytesWritten = 0;
647        long msgHandle;
648
649        try {
650            outBytes = mOutContent.getMessage(handle, appParams);
651            outStream = op.openOutputStream();
652
653        } catch (IOException e) {
654            Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e);
655            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
656        } catch (IllegalArgumentException e) {
657            Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - sending OBEX_HTTP_BAD_REQUEST", e);
658            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
659        }
660
661        maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers.
662
663        if(outBytes != null) {
664            try {
665                while (bytesWritten < outBytes.length && sIsAborted == false) {
666                    bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten);
667                    outStream.write(outBytes, bytesWritten, bytesToWrite);
668                    bytesWritten += bytesToWrite;
669                }
670            } catch (IOException e) {
671                // We were probably aborted or disconnected
672            } finally {
673                if(outStream != null) {
674                    try {
675                        outStream.close();
676                    } catch (IOException e) {
677                        // If an error occurs during close, there is no more cleanup to do
678                    }
679                }
680            }
681            if(bytesWritten == outBytes.length)
682                return ResponseCodes.OBEX_HTTP_OK;
683            else
684                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
685        }
686
687        return ResponseCodes.OBEX_HTTP_OK;
688    }
689
690
691    private static final void logHeader(HeaderSet hs) {
692        Log.v(TAG, "Dumping HeaderSet " + hs.toString());
693        try {
694            Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID));
695            Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME));
696            Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE));
697            Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET));
698            Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO));
699            Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER));
700        } catch (IOException e) {
701            Log.e(TAG, "dump HeaderSet error " + e);
702        }
703        Log.v(TAG, "NEW!!! Dumping HeaderSet END");
704    }
705}
706