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 BluetoothMapObexServer(Handler callback, Context context, BluetoothMapContentObserver observer, int masId, BluetoothMapEmailSettingsItem account, boolean enableSmsMms)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 */ acquireUnstableContentProviderOrThrow()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 */ buildFolderStructure()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 */ addBaseFolders(BluetoothMapFolderElement root)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 */ addSmsMmsFolders(BluetoothMapFolderElement root)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 */ addEmailFolders(BluetoothMapFolderElement parentFolder)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 onConnect(final HeaderSet request, HeaderSet reply)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 onDisconnect(final HeaderSet req, final HeaderSet resp)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 onAbort(HeaderSet request, HeaderSet reply)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 onPut(final Operation op)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 updateInbox()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 getFolderElementFromName(String folderName)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 pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams)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 setMessageStatus(String msgHandle, BluetoothMapAppParams appParams)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 onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, final boolean create)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 onClose()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 onGet(Operation op)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 */ sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, String folderName)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 */ sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams)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 */ sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams)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 notifyUpdateWakeLock()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 logHeader(HeaderSet hs)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