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