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