1 /* 2 * Copyright (C) 2014 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 android.content.ContentProviderClient; 18 import android.content.ContentResolver; 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.net.Uri; 22 import android.os.Bundle; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.os.RemoteException; 26 import com.android.bluetooth.mapapi.BluetoothMapContract; 27 import android.text.format.DateUtils; 28 import android.util.Log; 29 30 import com.android.bluetooth.map.BluetoothMapUtils; 31 import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 32 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.OutputStream; 36 import java.text.ParseException; 37 import java.util.Arrays; 38 import java.util.Calendar; 39 40 import javax.obex.HeaderSet; 41 import javax.obex.Operation; 42 import javax.obex.ResponseCodes; 43 import javax.obex.ServerRequestHandler; 44 45 46 public class BluetoothMapObexServer extends ServerRequestHandler { 47 48 private static final String TAG = "BluetoothMapObexServer"; 49 50 private static final boolean D = BluetoothMapService.DEBUG; 51 private static final boolean V = BluetoothMapService.VERBOSE; 52 53 private static final int UUID_LENGTH = 16; 54 55 private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; 56 57 /* OBEX header and value used to detect clients that support threadId in the message listing. */ 58 private static final int THREADED_MAIL_HEADER_ID = 0xFA; 59 private static final long THREAD_MAIL_KEY = 0x534c5349; 60 61 // 128 bit UUID for MAP 62 private static final byte[] MAP_TARGET = new byte[] { 63 (byte)0xBB, (byte)0x58, (byte)0x2B, (byte)0x40, 64 (byte)0x42, (byte)0x0C, (byte)0x11, (byte)0xDB, 65 (byte)0xB0, (byte)0xDE, (byte)0x08, (byte)0x00, 66 (byte)0x20, (byte)0x0C, (byte)0x9A, (byte)0x66 67 }; 68 69 /* Message types */ 70 private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing"; 71 private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing"; 72 private static final String TYPE_MESSAGE = "x-bt/message"; 73 private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus"; 74 private static final String TYPE_SET_NOTIFICATION_REGISTRATION = "x-bt/MAP-NotificationRegistration"; 75 private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate"; 76 77 private BluetoothMapFolderElement mCurrentFolder; 78 79 private BluetoothMapContentObserver mObserver = null; 80 81 private Handler mCallback = null; 82 83 private Context mContext; 84 85 private boolean mIsAborted = false; 86 87 BluetoothMapContent mOutContent; 88 89 private String mBaseEmailUriString = null; 90 private long mAccountId = 0; 91 private BluetoothMapEmailSettingsItem mAccount = null; 92 private Uri mEmailFolderUri = null; 93 94 private int mMasId = 0; 95 96 private boolean mEnableSmsMms = false; 97 private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing 98 private String mAuthority; 99 private ContentResolver mResolver; 100 private ContentProviderClient mProviderClient = null; 101 102 public BluetoothMapObexServer(Handler callback, 103 Context context, 104 BluetoothMapContentObserver observer, 105 int masId, 106 BluetoothMapEmailSettingsItem account, 107 boolean enableSmsMms) throws RemoteException { 108 super(); 109 mCallback = callback; 110 mContext = context; 111 mObserver = observer; 112 mEnableSmsMms = enableSmsMms; 113 mAccount = account; 114 mMasId = masId; 115 116 if(account != null && account.getProviderAuthority() != null) { 117 mAccountId = account.getAccountId(); 118 mAuthority = account.getProviderAuthority(); 119 mResolver = mContext.getContentResolver(); 120 if (D) Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId); 121 mBaseEmailUriString = account.mBase_uri + "/"; 122 if (D) Log.d(TAG, "BluetoothMapObexServer(): emailBaseUri=" + mBaseEmailUriString); 123 mEmailFolderUri = BluetoothMapContract.buildFolderUri(mAuthority, 124 Long.toString(mAccountId)); 125 if (D) Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri); 126 mProviderClient = acquireUnstableContentProviderOrThrow(); 127 } 128 129 buildFolderStructure(); /* Build the default folder structure, and set 130 mCurrentFolder to root folder */ 131 mObserver.setFolderStructure(mCurrentFolder.getRoot()); 132 133 mOutContent = new BluetoothMapContent(mContext, mBaseEmailUriString); 134 135 } 136 137 /** 138 * 139 */ 140 private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException{ 141 ContentProviderClient providerClient = mResolver.acquireUnstableContentProviderClient(mAuthority); 142 if (providerClient == null) { 143 throw new RemoteException("Failed to acquire provider for " + mAuthority); 144 } 145 providerClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 146 return providerClient; 147 } 148 149 /** 150 * Build the default minimal folder structure, as defined in the MAP specification. 151 */ 152 private void buildFolderStructure() throws RemoteException{ 153 mCurrentFolder = new BluetoothMapFolderElement("root", null); // This will be the root element 154 BluetoothMapFolderElement tmpFolder; 155 tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom 156 tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg 157 158 addBaseFolders(tmpFolder); // Add the mandatory folders 159 160 if(mEnableSmsMms) { 161 addSmsMmsFolders(tmpFolder); 162 } 163 if(mEmailFolderUri != null) { 164 if (D) Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString()); 165 addEmailFolders(tmpFolder); 166 } 167 } 168 169 /** 170 * Add 171 * @param root 172 */ 173 private void addBaseFolders(BluetoothMapFolderElement root) { 174 root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox 175 root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX); 176 root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT); 177 root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED); 178 } 179 180 181 /** 182 * Add 183 * @param root 184 */ 185 private void addSmsMmsFolders(BluetoothMapFolderElement root) { 186 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox 187 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX); 188 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT); 189 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED); 190 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DRAFT); 191 } 192 193 194 /** 195 * Recursively adds folders based on the folders in the email content provider. 196 * Add a content observer? - to refresh the folder list if any change occurs. 197 * Consider simply deleting the entire table, and then rebuild using buildFolderStructure() 198 * WARNING: there is no way to notify the client about these changes - hence 199 * we need to either keep the folder structure constant, disconnect or fail anything 200 * referring to currentFolder. 201 * It is unclear what to set as current folder to be able to go one level up... 202 * The best solution would be to keep the folder structure constant during a connection. 203 * @param folder the parent folder to which subFolders needs to be added. The 204 * folder.getEmailFolderId() will be used to query sub-folders. 205 * Use a parentFolder with id -1 to get all folders from root. 206 */ 207 private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException { 208 // Select all parent folders 209 BluetoothMapFolderElement newFolder; 210 211 String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID + 212 " = " + parentFolder.getEmailFolderId(); 213 Cursor c = mProviderClient.query(mEmailFolderUri, 214 BluetoothMapContract.BT_FOLDER_PROJECTION, where, null, null); 215 if (c != null) { 216 c.moveToPosition(-1); 217 while (c.moveToNext()) { 218 String name = c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME)); 219 long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID)); 220 newFolder = parentFolder.addEmailFolder(name, id); 221 addEmailFolders(newFolder); // Use recursion to add any sub folders 222 } 223 c.close(); 224 } else { 225 if (D) Log.d(TAG, "addEmailFolders(): no elements found"); 226 } 227 } 228 229 @Override 230 public int onConnect(final HeaderSet request, HeaderSet reply) { 231 if (D) Log.d(TAG, "onConnect():"); 232 if (V) logHeader(request); 233 mThreadIdSupport = false; // Always assume not supported at new connect. 234 notifyUpdateWakeLock(); 235 Long threadedMailKey = null; 236 try { 237 byte[] uuid = (byte[])request.getHeader(HeaderSet.TARGET); 238 threadedMailKey = (Long)request.getHeader(THREADED_MAIL_HEADER_ID); 239 if (uuid == null) { 240 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 241 } 242 if (D) Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid)); 243 244 if (uuid.length != UUID_LENGTH) { 245 Log.w(TAG, "Wrong UUID length"); 246 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 247 } 248 for (int i = 0; i < UUID_LENGTH; i++) { 249 if (uuid[i] != MAP_TARGET[i]) { 250 Log.w(TAG, "Wrong UUID"); 251 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 252 } 253 } 254 reply.setHeader(HeaderSet.WHO, uuid); 255 } catch (IOException e) { 256 Log.e(TAG,"Exception during onConnect:", e); 257 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 258 } 259 260 try { 261 byte[] remote = (byte[])request.getHeader(HeaderSet.WHO); 262 if (remote != null) { 263 if (D) Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); 264 reply.setHeader(HeaderSet.TARGET, remote); 265 } 266 if(threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY) 267 { 268 /* If the client provides the correct key we enable threaded e-mail support 269 * and reply to the client that we support the requested feature. 270 * This is currently an Android only feature. */ 271 mThreadIdSupport = true; 272 reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY); 273 } 274 } catch (IOException e) { 275 Log.e(TAG,"Exception during onConnect:", e); 276 mThreadIdSupport = false; 277 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 278 } 279 280 if (V) Log.v(TAG, "onConnect(): uuid is ok, will send out " + 281 "MSG_SESSION_ESTABLISHED msg."); 282 283 if(mCallback != null) { 284 Message msg = Message.obtain(mCallback); 285 msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED; 286 msg.sendToTarget(); 287 } 288 289 return ResponseCodes.OBEX_HTTP_OK; 290 } 291 292 @Override 293 public void onDisconnect(final HeaderSet req, final HeaderSet resp) { 294 if (D) Log.d(TAG, "onDisconnect(): enter"); 295 if (V) logHeader(req); 296 notifyUpdateWakeLock(); 297 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 298 if (mCallback != null) { 299 Message msg = Message.obtain(mCallback); 300 msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED; 301 msg.sendToTarget(); 302 if (V) Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out."); 303 } 304 } 305 306 @Override 307 public int onAbort(HeaderSet request, HeaderSet reply) { 308 if (D) Log.d(TAG, "onAbort(): enter."); 309 notifyUpdateWakeLock(); 310 mIsAborted = true; 311 return ResponseCodes.OBEX_HTTP_OK; 312 } 313 314 @Override 315 public int onPut(final Operation op) { 316 if (D) Log.d(TAG, "onPut(): enter"); 317 mIsAborted = false; 318 notifyUpdateWakeLock(); 319 HeaderSet request = null; 320 String type, name; 321 byte[] appParamRaw; 322 BluetoothMapAppParams appParams = null; 323 324 try { 325 request = op.getReceivedHeader(); 326 type = (String)request.getHeader(HeaderSet.TYPE); 327 328 name = (String)request.getHeader(HeaderSet.NAME); 329 appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER); 330 if(appParamRaw != null) 331 appParams = new BluetoothMapAppParams(appParamRaw); 332 if(D) Log.d(TAG,"type = " + type + ", name = " + name); 333 if (type.equals(TYPE_MESSAGE_UPDATE)) { 334 if(V) { 335 Log.d(TAG,"TYPE_MESSAGE_UPDATE:"); 336 } 337 return updateInbox(); 338 }else if(type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) { 339 if(V) { 340 Log.d(TAG,"TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " 341 + appParams.getNotificationStatus()); 342 } 343 return mObserver.setNotificationRegistration(appParams.getNotificationStatus()); 344 }else if(type.equals(TYPE_SET_MESSAGE_STATUS)) { 345 if(V) { 346 Log.d(TAG,"TYPE_SET_MESSAGE_STATUS: StatusIndicator: " 347 + appParams.getStatusIndicator() 348 + ", StatusValue: " + appParams.getStatusValue()); 349 } 350 return setMessageStatus(name, appParams); 351 } else if (type.equals(TYPE_MESSAGE)) { 352 if(V) { 353 Log.d(TAG,"TYPE_MESSAGE: Transparet: " + appParams.getTransparent() 354 + ", retry: " + appParams.getRetry() 355 + ", charset: " + appParams.getCharset()); 356 } 357 return pushMessage(op, name, appParams); 358 } 359 } catch (RemoteException e){ 360 //reload the providerClient and return error 361 try { 362 mProviderClient = acquireUnstableContentProviderOrThrow(); 363 }catch (RemoteException e2){ 364 //should not happen 365 } 366 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 367 }catch (Exception e) { 368 369 if(D) { 370 Log.e(TAG, "Exception occured while handling request",e); 371 } else { 372 Log.e(TAG, "Exception occured while handling request"); 373 } 374 if(mIsAborted) { 375 return ResponseCodes.OBEX_HTTP_OK; 376 } else { 377 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 378 } 379 } 380 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 381 } 382 383 private int updateInbox() throws RemoteException{ 384 if (mAccount != null) { 385 BluetoothMapFolderElement inboxFolder = mCurrentFolder.getEmailFolderByName( 386 BluetoothMapContract.FOLDER_NAME_INBOX); 387 if (inboxFolder != null) { 388 long accountId = mAccountId; 389 if (D) Log.d(TAG,"updateInbox inbox=" + inboxFolder.getName() + "id=" 390 + inboxFolder.getEmailFolderId()); 391 392 final Bundle extras = new Bundle(2); 393 if (accountId != -1) { 394 if (D) Log.d(TAG,"updateInbox accountId=" + accountId); 395 extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, 396 inboxFolder.getEmailFolderId()); 397 extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId); 398 } else { 399 // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED, 400 // i.e. if e.g. update not allowed on the mailbox 401 if (D) Log.d(TAG,"updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED"); 402 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 403 } 404 405 Uri emailUri = Uri.parse(mBaseEmailUriString); 406 if (D) Log.d(TAG,"updateInbox in: " + emailUri.toString()); 407 try { 408 if (D) Log.d(TAG,"updateInbox call()..."); 409 Bundle myBundle = mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null, extras); 410 if (myBundle != null) 411 return ResponseCodes.OBEX_HTTP_OK; 412 else { 413 if (D) Log.d(TAG,"updateInbox call failed"); 414 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 415 } 416 } catch (RemoteException e){ 417 mProviderClient = acquireUnstableContentProviderOrThrow(); 418 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 419 }catch (NullPointerException e) { 420 if(D) Log.e(TAG, "UpdateInbox - if uri or method is null", e); 421 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 422 423 } catch (IllegalArgumentException e) { 424 if(D) Log.e(TAG, "UpdateInbox - if uri is not known", e); 425 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 426 } 427 } 428 } 429 430 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 431 } 432 433 private BluetoothMapFolderElement getFolderElementFromName(String folderName) { 434 BluetoothMapFolderElement folderElement = null; 435 436 if(folderName == null || folderName.trim().isEmpty() ) { 437 folderElement = mCurrentFolder; 438 if(D) Log.d(TAG, "no folder name supplied, setting folder to current: " 439 + folderElement.getName()); 440 } else { 441 folderElement = mCurrentFolder.getSubFolder(folderName); 442 if(D) Log.d(TAG, "Folder name: " + folderName + " resulted in this element: " 443 + folderElement.getName()); 444 } 445 return folderElement; 446 } 447 448 private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams) { 449 if(appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 450 if(D) Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " + 451 "appParams.getCharset() = " + appParams.getCharset()); 452 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 453 } 454 InputStream bMsgStream = null; 455 try { 456 BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName); 457 if(folderElement == null) { 458 Log.w(TAG,"pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED"); 459 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 460 } else { 461 folderName = folderElement.getName(); 462 } 463 if (!folderName.equals(BluetoothMapContract.FOLDER_NAME_OUTBOX) && 464 !folderName.equals(BluetoothMapContract.FOLDER_NAME_DRAFT)) { 465 if(D) Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " + 466 "folderName=" + folderName); 467 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 468 } 469 470 /* - Read out the message 471 * - Decode into a bMessage 472 * - send it. 473 */ 474 BluetoothMapbMessage message; 475 bMsgStream = op.openInputStream(); 476 // Decode the messageBody 477 message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); 478 // Send message 479 if (mObserver == null || message == null) { 480 // Should not happen except at shutdown. 481 if(D) Log.w(TAG, "mObserver or parsed message not available" ); 482 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 483 } 484 485 if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getEmailFolderId() == -1)) || 486 ((message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA) || 487 message.getType().equals(TYPE.MMS)) && !folderElement.hasSmsMmsContent()) ) { 488 if(D) Log.w(TAG, "Wrong message type recieved" ); 489 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 490 } 491 492 long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseEmailUriString); 493 if (D) Log.d(TAG, "pushMessage handle: " + handle); 494 if (handle < 0) { 495 if(D) Log.w(TAG, "Message handle not created" ); 496 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen. 497 } 498 HeaderSet replyHeaders = new HeaderSet(); 499 String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType()); 500 if (D) Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType()); 501 replyHeaders.setHeader(HeaderSet.NAME, handleStr); 502 op.sendHeaders(replyHeaders); 503 504 } catch (RemoteException e) { 505 //reload the providerClient and return error 506 try { 507 mProviderClient = acquireUnstableContentProviderOrThrow(); 508 }catch (RemoteException e2){ 509 //should not happen 510 } 511 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 512 } catch (IllegalArgumentException e) { 513 if (D) Log.e(TAG, "Wrongly formatted bMessage received", e); 514 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 515 } catch (IOException e) { 516 if (D) Log.e(TAG, "Exception occured: ", e); 517 if(mIsAborted == true) { 518 if(D) Log.d(TAG, "PushMessage Operation Aborted"); 519 return ResponseCodes.OBEX_HTTP_OK; 520 } else { 521 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 522 } 523 } catch (Exception e) { 524 if (D) Log.e(TAG, "Exception:", e); 525 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 526 } finally { 527 if(bMsgStream != null) { 528 try { 529 bMsgStream.close(); 530 } catch (IOException e) {} 531 } 532 } 533 return ResponseCodes.OBEX_HTTP_OK; 534 } 535 536 private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) { 537 int indicator = appParams.getStatusIndicator(); 538 int value = appParams.getStatusValue(); 539 long handle; 540 BluetoothMapUtils.TYPE msgType; 541 542 if(indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || 543 value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER || 544 msgHandle == null) { 545 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 546 } 547 if (mObserver == null) { 548 if(D) Log.d(TAG, "Error: no mObserver!"); 549 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen. 550 } 551 552 try { 553 handle = BluetoothMapUtils.getCpHandle(msgHandle); 554 msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle); 555 if(D)Log.d(TAG,"setMessageStatus. Handle:" + handle+", MsgType: "+ msgType); 556 } catch (NumberFormatException e) { 557 Log.w(TAG, "Wrongly formatted message handle: " + msgHandle); 558 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 559 } 560 561 if( indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) { 562 if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder, 563 mBaseEmailUriString, value)) { 564 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 565 } 566 } else /* BluetoothMapAppParams.STATUS_INDICATOR_READ */ { 567 try{ 568 if (!mObserver.setMessageStatusRead(handle, msgType, mBaseEmailUriString, value)) { 569 if(D)Log.d(TAG,"not able to update the message"); 570 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 571 } 572 }catch(RemoteException e) { 573 if(D) Log.e(TAG,"Error in setMessageStatusRead()", e); 574 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 575 } 576 } 577 return ResponseCodes.OBEX_HTTP_OK; 578 } 579 580 @Override 581 public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, 582 final boolean create) { 583 String folderName; 584 BluetoothMapFolderElement folder; 585 notifyUpdateWakeLock(); 586 try { 587 folderName = (String)request.getHeader(HeaderSet.NAME); 588 } catch (Exception e) { 589 if(D) { 590 Log.e(TAG, "request headers error" , e); 591 } else { 592 Log.e(TAG, "request headers error"); 593 } 594 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 595 } 596 597 if (V) logHeader(request); 598 if (D) Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup 599 + " create: " + create); 600 601 if(backup == true){ 602 if(mCurrentFolder.getParent() != null) 603 mCurrentFolder = mCurrentFolder.getParent(); 604 else 605 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 606 } 607 608 if (folderName == null || folderName.trim().isEmpty()) { 609 if(backup == false) 610 mCurrentFolder = mCurrentFolder.getRoot(); 611 } 612 else { 613 folder = mCurrentFolder.getSubFolder(folderName); 614 if(folder != null) 615 mCurrentFolder = folder; 616 else 617 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 618 } 619 if (V) Log.d(TAG, "Current Folder: " + mCurrentFolder.getName()); 620 return ResponseCodes.OBEX_HTTP_OK; 621 } 622 623 @Override 624 public void onClose() { 625 if (mCallback != null) { 626 Message msg = Message.obtain(mCallback); 627 msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE; 628 msg.arg1 = mMasId; 629 msg.sendToTarget(); 630 if (D) Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); 631 632 } 633 if(mProviderClient != null){ 634 mProviderClient.release(); 635 mProviderClient = null; 636 } 637 638 } 639 640 @Override 641 public int onGet(Operation op) { 642 notifyUpdateWakeLock(); 643 mIsAborted = false; 644 HeaderSet request; 645 String type; 646 String name; 647 byte[] appParamRaw = null; 648 BluetoothMapAppParams appParams = null; 649 try { 650 request = op.getReceivedHeader(); 651 type = (String)request.getHeader(HeaderSet.TYPE); 652 name = (String)request.getHeader(HeaderSet.NAME); 653 appParamRaw = (byte[])request.getHeader(HeaderSet.APPLICATION_PARAMETER); 654 if(appParamRaw != null) 655 appParams = new BluetoothMapAppParams(appParamRaw); 656 657 if (V) logHeader(request); 658 if (D) Log.d(TAG, "OnGet type is " + type + " name is " + name); 659 660 if (type == null) { 661 if (V) Log.d(TAG, "type is null?" + type); 662 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 663 } 664 665 if (type.equals(TYPE_GET_FOLDER_LISTING)) { 666 if (V && appParams != null) { 667 Log.d(TAG,"TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() + 668 ", ListStartOffset = " + appParams.getStartOffset()); 669 } 670 return sendFolderListingRsp(op, appParams); // Block until all packets have been send. 671 } else if (type.equals(TYPE_GET_MESSAGE_LISTING)){ 672 if (V && appParams != null) { 673 Log.d(TAG,"TYPE_GET_MESSAGE_LISTING: MaxListCount = " + appParams.getMaxListCount() + 674 ", ListStartOffset = " + appParams.getStartOffset()); 675 Log.d(TAG,"SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " + 676 appParams.getParameterMask()); 677 Log.d(TAG,"FilterMessageType = " + appParams.getFilterMessageType() + 678 ", FilterPeriodBegin = " + appParams.getFilterPeriodBegin()); 679 Log.d(TAG,"FilterPeriodEnd = " + appParams.getFilterPeriodBegin() + 680 ", FilterReadStatus = " + appParams.getFilterReadStatus()); 681 Log.d(TAG,"FilterRecipient = " + appParams.getFilterRecipient() + 682 ", FilterOriginator = " + appParams.getFilterOriginator()); 683 Log.d(TAG,"FilterPriority = " + appParams.getFilterPriority()); 684 } 685 return sendMessageListingRsp(op, appParams, name); // Block until all packets have been send. 686 } else if (type.equals(TYPE_MESSAGE)){ 687 if(V && appParams != null) { 688 Log.d(TAG,"TYPE_MESSAGE (GET): Attachment = " + appParams.getAttachment() + 689 ", Charset = " + appParams.getCharset() + 690 ", FractionRequest = " + appParams.getFractionRequest()); 691 } 692 return sendGetMessageRsp(op, name, appParams); // Block until all packets have been send. 693 } else { 694 Log.w(TAG, "unknown type request: " + type); 695 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 696 } 697 698 } catch (IllegalArgumentException e) { 699 Log.e(TAG, "Exception:", e); 700 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 701 } catch (ParseException e) { 702 Log.e(TAG, "Exception:", e); 703 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 704 } catch (Exception e) { 705 if(D) { 706 Log.e(TAG, "Exception occured while handling request",e); 707 } else { 708 Log.e(TAG, "Exception occured while handling request"); 709 } 710 if(mIsAborted == true) { 711 if(D) Log.d(TAG, "onGet Operation Aborted"); 712 return ResponseCodes.OBEX_HTTP_OK; 713 } else { 714 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 715 } 716 } 717 } 718 719 /** 720 * Generate and send the message listing response based on an application 721 * parameter header. This function call will block until complete or aborted 722 * by the peer. Fragmentation of packets larger than the obex packet size 723 * will be handled by this function. 724 * 725 * @param op 726 * The OBEX operation. 727 * @param appParams 728 * The application parameter header 729 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 730 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 731 */ 732 private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName){ 733 OutputStream outStream = null; 734 byte[] outBytes = null; 735 int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize; 736 boolean hasUnread = false; 737 HeaderSet replyHeaders = new HeaderSet(); 738 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 739 BluetoothMapMessageListing outList; 740 if(appParams == null){ 741 appParams = new BluetoothMapAppParams(); 742 appParams.setMaxListCount(1024); 743 appParams.setStartOffset(0); 744 } 745 746 BluetoothMapFolderElement folderToList = getFolderElementFromName(folderName); 747 if(folderToList == null) { 748 Log.w(TAG,"sendMessageListingRsp: folderToList == null - sending OBEX_HTTP_BAD_REQUEST"); 749 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 750 } 751 752 // Check to see if we only need to send the size - hence no need to encode. 753 try { 754 // Open the OBEX body stream 755 outStream = op.openOutputStream(); 756 757 if(appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 758 appParams.setMaxListCount(1024); 759 760 if(appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 761 appParams.setStartOffset(0); 762 763 if(appParams.getMaxListCount() != 0) { 764 outList = mOutContent.msgListing(folderToList, appParams); 765 // Generate the byte stream 766 outAppParams.setMessageListingSize(outList.getCount()); 767 outBytes = outList.encode(mThreadIdSupport); // Include thread ID for clients that supports it. 768 hasUnread = outList.hasUnread(); 769 } 770 else { 771 listSize = mOutContent.msgListingSize(folderToList, appParams); 772 hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams); 773 outAppParams.setMessageListingSize(listSize); 774 op.noBodyHeader(); 775 } 776 777 // Build the application parameter header 778 779 // let the peer know if there are unread messages in the list 780 if(hasUnread) { 781 outAppParams.setNewMessage(1); 782 }else{ 783 outAppParams.setNewMessage(0); 784 } 785 786 outAppParams.setMseTime(Calendar.getInstance().getTime().getTime()); 787 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams()); 788 op.sendHeaders(replyHeaders); 789 790 } catch (IOException e) { 791 Log.w(TAG,"sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e); 792 if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} } 793 if(mIsAborted == true) { 794 if(D) Log.d(TAG, "sendMessageListingRsp Operation Aborted"); 795 return ResponseCodes.OBEX_HTTP_OK; 796 } else { 797 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 798 } 799 } catch (IllegalArgumentException e) { 800 Log.w(TAG,"sendMessageListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST", e); 801 if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} } 802 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 803 } 804 805 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 806 if(outBytes != null) { 807 try { 808 while (bytesWritten < outBytes.length && mIsAborted == false) { 809 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 810 outStream.write(outBytes, bytesWritten, bytesToWrite); 811 bytesWritten += bytesToWrite; 812 } 813 } catch (IOException e) { 814 if(D) Log.w(TAG,e); 815 // We were probably aborted or disconnected 816 } finally { 817 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 818 } 819 if(bytesWritten != outBytes.length && !mIsAborted) { 820 Log.w(TAG,"sendMessageListingRsp: bytesWritten != outBytes.length - sending OBEX_HTTP_BAD_REQUEST"); 821 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 822 } 823 } else { 824 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 825 } 826 return ResponseCodes.OBEX_HTTP_OK; 827 } 828 829 /** 830 * Generate and send the Folder listing response based on an application 831 * parameter header. This function call will block until complete or aborted 832 * by the peer. Fragmentation of packets larger than the obex packet size 833 * will be handled by this function. 834 * 835 * @param op 836 * The OBEX operation. 837 * @param appParams 838 * The application parameter header 839 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 840 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 841 */ 842 private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams){ 843 OutputStream outStream = null; 844 byte[] outBytes = null; 845 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 846 int maxChunkSize, bytesWritten = 0; 847 HeaderSet replyHeaders = new HeaderSet(); 848 int bytesToWrite, maxListCount, listStartOffset; 849 if(appParams == null){ 850 appParams = new BluetoothMapAppParams(); 851 appParams.setMaxListCount(1024); 852 } 853 854 if(V) 855 Log.v(TAG,"sendFolderList for " + mCurrentFolder.getName()); 856 857 try { 858 maxListCount = appParams.getMaxListCount(); 859 listStartOffset = appParams.getStartOffset(); 860 861 if(listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 862 listStartOffset = 0; 863 864 if(maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 865 maxListCount = 1024; 866 867 if(maxListCount != 0) 868 { 869 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount); 870 outStream = op.openOutputStream(); 871 } 872 873 // Build and set the application parameter header 874 outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount()); 875 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.EncodeParams()); 876 op.sendHeaders(replyHeaders); 877 878 } catch (IOException e1) { 879 Log.w(TAG,"sendFolderListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1); 880 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 881 if(mIsAborted == true) { 882 if(D) Log.d(TAG, "sendFolderListingRsp Operation Aborted"); 883 return ResponseCodes.OBEX_HTTP_OK; 884 } else { 885 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 886 } 887 } catch (IllegalArgumentException e1) { 888 Log.w(TAG,"sendFolderListingRsp: IllegalArgumentException - sending OBEX_HTTP_BAD_REQUEST Exception:", e1); 889 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 890 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 891 } 892 893 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 894 895 if(outBytes != null) { 896 try { 897 while (bytesWritten < outBytes.length && mIsAborted == false) { 898 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 899 outStream.write(outBytes, bytesWritten, bytesToWrite); 900 bytesWritten += bytesToWrite; 901 } 902 } catch (IOException e) { 903 // We were probably aborted or disconnected 904 } finally { 905 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 906 } 907 if(V) 908 Log.v(TAG,"sendFolderList sent " + bytesWritten + " bytes out of "+ outBytes.length); 909 if(bytesWritten == outBytes.length || mIsAborted) 910 return ResponseCodes.OBEX_HTTP_OK; 911 else 912 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 913 } 914 915 return ResponseCodes.OBEX_HTTP_OK; 916 } 917 918 /** 919 * Generate and send the get message response based on an application 920 * parameter header and a handle. 921 * 922 * @param op 923 * The OBEX operation. 924 * @param appParams 925 * The application parameter header 926 * @param handle 927 * The handle of the requested message 928 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 929 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 930 */ 931 private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams){ 932 OutputStream outStream = null; 933 byte[] outBytes = null; 934 int maxChunkSize, bytesToWrite, bytesWritten = 0; 935 936 try { 937 outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder); 938 outStream = op.openOutputStream(); 939 940 // If it is a fraction request of Email message, set header before responding 941 if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL)) && 942 (appParams.getFractionRequest() == 943 BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) { 944 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();; 945 HeaderSet replyHeaders = new HeaderSet(); 946 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST); 947 // Build and set the application parameter header 948 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, 949 outAppParams.EncodeParams()); 950 op.sendHeaders(replyHeaders); 951 if(V) Log.v(TAG,"sendGetMessageRsp fractionRequest - " + 952 "set FRACTION_DELIVER_LAST header"); 953 } 954 955 } catch (IOException e) { 956 Log.w(TAG,"sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e); 957 if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} } 958 if(mIsAborted == true) { 959 if(D) Log.d(TAG, "sendGetMessageRsp Operation Aborted"); 960 return ResponseCodes.OBEX_HTTP_OK; 961 } else { 962 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 963 } 964 } catch (IllegalArgumentException e) { 965 Log.w(TAG,"sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " + 966 "sending OBEX_HTTP_BAD_REQUEST", e); 967 if(outStream != null) { try { outStream.close(); } catch (IOException ex) {} } 968 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 969 } 970 971 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 972 973 if(outBytes != null) { 974 try { 975 while (bytesWritten < outBytes.length && mIsAborted == false) { 976 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 977 outStream.write(outBytes, bytesWritten, bytesToWrite); 978 bytesWritten += bytesToWrite; 979 } 980 } catch (IOException e) { 981 // We were probably aborted or disconnected 982 if(D && e.getMessage().equals("Abort Received")) { 983 Log.w(TAG, "getMessage() Aborted...", e); 984 } 985 } finally { 986 if(outStream != null) { try { outStream.close(); } catch (IOException e) {} } 987 } 988 if(bytesWritten == outBytes.length || mIsAborted) 989 return ResponseCodes.OBEX_HTTP_OK; 990 else 991 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 992 } 993 994 return ResponseCodes.OBEX_HTTP_OK; 995 } 996 997 private void notifyUpdateWakeLock() { 998 if(mCallback != null) { 999 Message msg = Message.obtain(mCallback); 1000 msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK; 1001 msg.sendToTarget(); 1002 } 1003 } 1004 1005 private static final void logHeader(HeaderSet hs) { 1006 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1007 try { 1008 Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID)); 1009 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1010 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1011 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1012 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1013 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1014 } catch (IOException e) { 1015 Log.e(TAG, "dump HeaderSet error " + e); 1016 } 1017 Log.v(TAG, "NEW!!! Dumping HeaderSet END"); 1018 } 1019 } 1020