BluetoothMapObexServer.java revision 2aeba34a2dbb09d509a982dedd1689164317886a
1/* 2* Copyright (C) 2015 Samsung System LSI 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15package com.android.bluetooth.map; 16 17import android.content.ContentProviderClient; 18import android.content.ContentResolver; 19import android.content.Context; 20import android.database.Cursor; 21import android.net.Uri; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.Message; 25import android.os.ParcelUuid; 26import android.os.RemoteException; 27import android.os.UserManager; 28import android.telephony.TelephonyManager; 29import android.text.format.DateUtils; 30import android.util.Log; 31 32import com.android.bluetooth.SignedLongLong; 33import com.android.bluetooth.map.BluetoothMapUtils.TYPE; 34import com.android.bluetooth.mapapi.BluetoothMapContract; 35 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39import java.text.ParseException; 40import java.util.Arrays; 41import java.util.Calendar; 42 43import javax.obex.HeaderSet; 44import javax.obex.Operation; 45import javax.obex.ResponseCodes; 46import javax.obex.ServerRequestHandler; 47 48 49public class BluetoothMapObexServer extends ServerRequestHandler { 50 51 private static final String TAG = "BluetoothMapObexServer"; 52 53 private static final boolean D = BluetoothMapService.DEBUG; 54 private static final boolean V = BluetoothMapService.VERBOSE; 55 56 private static final int UUID_LENGTH = 16; 57 58 private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; 59 60 /* OBEX header and value used to detect clients that support threadId in the message listing. */ 61 private static final int THREADED_MAIL_HEADER_ID = 0xFA; 62 private static final long THREAD_MAIL_KEY = 0x534c5349; 63 64 // 128 bit UUID for MAP 65 private static final byte[] MAP_TARGET = new byte[]{ 66 (byte) 0xBB, 67 (byte) 0x58, 68 (byte) 0x2B, 69 (byte) 0x40, 70 (byte) 0x42, 71 (byte) 0x0C, 72 (byte) 0x11, 73 (byte) 0xDB, 74 (byte) 0xB0, 75 (byte) 0xDE, 76 (byte) 0x08, 77 (byte) 0x00, 78 (byte) 0x20, 79 (byte) 0x0C, 80 (byte) 0x9A, 81 (byte) 0x66 82 }; 83 public static final ParcelUuid MAP = 84 ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB"); 85 public static final ParcelUuid MNS = 86 ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); 87 public static final ParcelUuid MAS = 88 ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB"); 89 /* Message types */ 90 private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing"; 91 private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing"; 92 private static final String TYPE_GET_CONVO_LISTING = "x-bt/MAP-convo-listing"; 93 private static final String TYPE_MESSAGE = "x-bt/message"; 94 private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus"; 95 private static final String TYPE_SET_NOTIFICATION_REGISTRATION = 96 "x-bt/MAP-NotificationRegistration"; 97 private static final String TYPE_MESSAGE_UPDATE = "x-bt/MAP-messageUpdate"; 98 private static final String TYPE_GET_MAS_INSTANCE_INFORMATION = "x-bt/MASInstanceInformation"; 99 private static final String TYPE_SET_OWNER_STATUS = "x-bt/participant"; 100 private static final String TYPE_SET_NOTIFICATION_FILTER = "x-bt/MAP-notification-filter"; 101 102 private static final int MAS_INSTANCE_INFORMATION_LENGTH = 200; 103 104 private BluetoothMapFolderElement mCurrentFolder; 105 private BluetoothMapContentObserver mObserver = null; 106 private Handler mCallback = null; 107 private Context mContext; 108 private boolean mIsAborted = false; 109 BluetoothMapContent mOutContent; 110 private String mBaseUriString = null; 111 private long mAccountId = 0; 112 private BluetoothMapAccountItem mAccount = null; 113 private Uri mEmailFolderUri = null; 114 private int mMasId = 0; 115 private BluetoothMapMasInstance mMasInstance; // TODO: change to interface? 116 // updated during connect if remote has alternative value 117 private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK; 118 private boolean mEnableSmsMms = false; 119 private boolean mThreadIdSupport = false; // true if peer supports threadId in msg listing 120 // Defaults message version is 1.0 but 1.1+ if feature bit is set 121 private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR; 122 private String mAuthority; 123 private ContentResolver mResolver; 124 private ContentProviderClient mProviderClient = null; 125 126 public BluetoothMapObexServer(Handler callback, Context context, 127 BluetoothMapContentObserver observer, BluetoothMapMasInstance mas, 128 BluetoothMapAccountItem account, boolean enableSmsMms) throws RemoteException { 129 super(); 130 mCallback = callback; 131 mContext = context; 132 mObserver = observer; 133 mEnableSmsMms = enableSmsMms; 134 mAccount = account; 135 mMasId = mas.getMasId(); 136 mMasInstance = mas; 137 mRemoteFeatureMask = mMasInstance.getRemoteFeatureMask(); 138 139 if (account != null && account.getProviderAuthority() != null) { 140 mAccountId = account.getAccountId(); 141 mAuthority = account.getProviderAuthority(); 142 mResolver = mContext.getContentResolver(); 143 if (D) { 144 Log.d(TAG, "BluetoothMapObexServer(): accountId=" + mAccountId); 145 } 146 mBaseUriString = account.mBase_uri + "/"; 147 if (D) { 148 Log.d(TAG, "BluetoothMapObexServer(): baseUri=" + mBaseUriString); 149 } 150 if (account.getType() == TYPE.EMAIL) { 151 mEmailFolderUri = 152 BluetoothMapContract.buildFolderUri(mAuthority, Long.toString(mAccountId)); 153 if (D) { 154 Log.d(TAG, "BluetoothMapObexServer(): mEmailFolderUri=" + mEmailFolderUri); 155 } 156 } 157 mProviderClient = acquireUnstableContentProviderOrThrow(); 158 } 159 160 buildFolderStructure(); /* Build the default folder structure, and set 161 mCurrentFolder to root folder */ 162 mObserver.setFolderStructure(mCurrentFolder.getRoot()); 163 164 mOutContent = new BluetoothMapContent(mContext, mAccount, mMasInstance); 165 166 } 167 168 /** 169 * 170 */ 171 private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException { 172 ContentProviderClient providerClient = 173 mResolver.acquireUnstableContentProviderClient(mAuthority); 174 if (providerClient == null) { 175 throw new RemoteException("Failed to acquire provider for " + mAuthority); 176 } 177 providerClient.setDetectNotResponding(PROVIDER_ANR_TIMEOUT); 178 return providerClient; 179 } 180 181 /** 182 * Build the default minimal folder structure, as defined in the MAP specification. 183 */ 184 private void buildFolderStructure() throws RemoteException { 185 //This will be the root element 186 mCurrentFolder = new BluetoothMapFolderElement("root", null); 187 mCurrentFolder.setHasSmsMmsContent(mEnableSmsMms); 188 boolean hasIM = false; 189 boolean hasEmail = false; 190 if (mAccount != null) { 191 if (mAccount.getType() == TYPE.IM) { 192 hasIM = true; 193 } 194 if (mAccount.getType() == TYPE.EMAIL) { 195 hasEmail = true; 196 } 197 } 198 mCurrentFolder.setHasImContent(hasIM); 199 mCurrentFolder.setHasEmailContent(hasEmail); 200 201 BluetoothMapFolderElement tmpFolder; 202 tmpFolder = mCurrentFolder.addFolder("telecom"); // root/telecom 203 tmpFolder.setHasSmsMmsContent(mEnableSmsMms); 204 tmpFolder.setHasImContent(hasIM); 205 tmpFolder.setHasEmailContent(hasEmail); 206 207 tmpFolder = tmpFolder.addFolder("msg"); // root/telecom/msg 208 tmpFolder.setHasSmsMmsContent(mEnableSmsMms); 209 tmpFolder.setHasImContent(hasIM); 210 tmpFolder.setHasEmailContent(hasEmail); 211 212 // Add the mandatory folders 213 addBaseFolders(tmpFolder); 214 if (mEnableSmsMms) { 215 addSmsMmsFolders(tmpFolder); 216 } 217 if (hasEmail) { 218 if (D) { 219 Log.d(TAG, "buildFolderStructure(): " + mEmailFolderUri.toString()); 220 } 221 addEmailFolders(tmpFolder); 222 } 223 if (hasIM) { 224 addImFolders(tmpFolder); 225 } 226 } 227 228 /** 229 * Add base (Inbox/Outbox/Sent/Deleted) 230 * @param root 231 */ 232 private void addBaseFolders(BluetoothMapFolderElement root) { 233 root.addFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox 234 root.addFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX); 235 root.addFolder(BluetoothMapContract.FOLDER_NAME_SENT); 236 root.addFolder(BluetoothMapContract.FOLDER_NAME_DELETED); 237 } 238 239 /** 240 * Add SMS / MMS Base folders 241 * @param root 242 */ 243 private void addSmsMmsFolders(BluetoothMapFolderElement root) { 244 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_INBOX); // root/telecom/msg/inbox 245 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX); 246 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_SENT); 247 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DELETED); 248 root.addSmsMmsFolder(BluetoothMapContract.FOLDER_NAME_DRAFT); 249 } 250 251 252 private void addImFolders(BluetoothMapFolderElement root) throws RemoteException { 253 // Select all parent folders 254 root.addImFolder(BluetoothMapContract.FOLDER_NAME_INBOX, 255 BluetoothMapContract.FOLDER_ID_INBOX); // root/telecom/msg/inbox 256 root.addImFolder(BluetoothMapContract.FOLDER_NAME_OUTBOX, 257 BluetoothMapContract.FOLDER_ID_OUTBOX); 258 root.addImFolder(BluetoothMapContract.FOLDER_NAME_SENT, 259 BluetoothMapContract.FOLDER_ID_SENT); 260 root.addImFolder(BluetoothMapContract.FOLDER_NAME_DELETED, 261 BluetoothMapContract.FOLDER_ID_DELETED); 262 root.addImFolder(BluetoothMapContract.FOLDER_NAME_DRAFT, 263 BluetoothMapContract.FOLDER_ID_DRAFT); 264 } 265 266 /** 267 * Recursively adds folders based on the folders in the email content provider. 268 * Add a content observer? - to refresh the folder list if any change occurs. 269 * Consider simply deleting the entire table, and then rebuild using 270 * buildFolderStructure() 271 * WARNING: there is no way to notify the client about these changes - hence 272 * we need to either keep the folder structure constant, disconnect or fail anything 273 * referring to currentFolder. 274 * It is unclear what to set as current folder to be able to go one level up... 275 * The best solution would be to keep the folder structure constant during a connection. 276 * @param folder the parent folder to which subFolders needs to be added. The 277 * folder.getFolderId() will be used to query sub-folders. 278 * Use a parentFolder with id -1 to get all folders from root. 279 */ 280 private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException { 281 // Select all parent folders 282 BluetoothMapFolderElement newFolder; 283 284 String where = BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID + " = " 285 + parentFolder.getFolderId(); 286 Cursor c = mProviderClient.query(mEmailFolderUri, BluetoothMapContract.BT_FOLDER_PROJECTION, 287 where, null, null); 288 try { 289 if (c != null) { 290 c.moveToPosition(-1); 291 while (c.moveToNext()) { 292 String name = 293 c.getString(c.getColumnIndex(BluetoothMapContract.FolderColumns.NAME)); 294 long id = c.getLong(c.getColumnIndex(BluetoothMapContract.FolderColumns._ID)); 295 newFolder = parentFolder.addEmailFolder(name, id); 296 addEmailFolders(newFolder); // Use recursion to add any sub folders 297 } 298 299 } else { 300 if (D) { 301 Log.d(TAG, "addEmailFolders(): no elements found"); 302 } 303 } 304 } finally { 305 if (c != null) { 306 c.close(); 307 } 308 } 309 } 310 311 @Override 312 public boolean isSrmSupported() { 313 // TODO: Update based on the transport used 314 return true; 315 } 316 317 public int getRemoteFeatureMask() { 318 return mRemoteFeatureMask; 319 } 320 321 public void setRemoteFeatureMask(int mRemoteFeatureMask) { 322 if (D) { 323 Log.d(TAG, "setRemoteFeatureMask() " + Integer.toHexString(mRemoteFeatureMask)); 324 } 325 this.mRemoteFeatureMask = mRemoteFeatureMask; 326 this.mOutContent.setRemoteFeatureMask(mRemoteFeatureMask); 327 } 328 329 @Override 330 public int onConnect(final HeaderSet request, HeaderSet reply) { 331 if (D) { 332 Log.d(TAG, "onConnect():"); 333 } 334 if (V) { 335 logHeader(request); 336 } 337 mThreadIdSupport = false; // Always assume not supported at new connect. 338 //always assume version 1.0 to start with 339 mMessageVersion = BluetoothMapUtils.MAP_V10_STR; 340 notifyUpdateWakeLock(); 341 Long threadedMailKey = null; 342 try { 343 byte[] uuid = (byte[]) request.getHeader(HeaderSet.TARGET); 344 threadedMailKey = (Long) request.getHeader(THREADED_MAIL_HEADER_ID); 345 if (uuid == null) { 346 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 347 } 348 if (D) { 349 Log.d(TAG, "onConnect(): uuid=" + Arrays.toString(uuid)); 350 } 351 352 if (uuid.length != UUID_LENGTH) { 353 Log.w(TAG, "Wrong UUID length"); 354 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 355 } 356 for (int i = 0; i < UUID_LENGTH; i++) { 357 if (uuid[i] != MAP_TARGET[i]) { 358 Log.w(TAG, "Wrong UUID"); 359 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 360 } 361 } 362 reply.setHeader(HeaderSet.WHO, uuid); 363 } catch (IOException e) { 364 Log.e(TAG, "Exception during onConnect:", e); 365 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 366 } 367 368 try { 369 byte[] remote = (byte[]) request.getHeader(HeaderSet.WHO); 370 if (remote != null) { 371 if (D) { 372 Log.d(TAG, "onConnect(): remote=" + Arrays.toString(remote)); 373 } 374 reply.setHeader(HeaderSet.TARGET, remote); 375 } 376 if (threadedMailKey != null && threadedMailKey.longValue() == THREAD_MAIL_KEY) { 377 /* If the client provides the correct key we enable threaded e-mail support 378 * and reply to the client that we support the requested feature. 379 * This is currently an Android only feature. */ 380 mThreadIdSupport = true; 381 reply.setHeader(THREADED_MAIL_HEADER_ID, THREAD_MAIL_KEY); 382 } 383 } catch (IOException e) { 384 Log.e(TAG, "Exception during onConnect:", e); 385 mThreadIdSupport = false; 386 return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR; 387 } 388 389 if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) 390 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT) { 391 mThreadIdSupport = true; 392 } 393 394 if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT) 395 == BluetoothMapUtils.MAP_FEATURE_MESSAGE_FORMAT_V11_BIT) { 396 mMessageVersion = BluetoothMapUtils.MAP_V11_STR; 397 } 398 399 if (V) { 400 Log.v(TAG, "onConnect(): uuid is ok, will send out " + "MSG_SESSION_ESTABLISHED msg."); 401 } 402 403 if (mCallback != null) { 404 Message msg = Message.obtain(mCallback); 405 msg.what = BluetoothMapService.MSG_SESSION_ESTABLISHED; 406 msg.sendToTarget(); 407 } 408 409 return ResponseCodes.OBEX_HTTP_OK; 410 } 411 412 @Override 413 public void onDisconnect(final HeaderSet req, final HeaderSet resp) { 414 if (D) { 415 Log.d(TAG, "onDisconnect(): enter"); 416 } 417 if (V) { 418 logHeader(req); 419 } 420 notifyUpdateWakeLock(); 421 resp.responseCode = ResponseCodes.OBEX_HTTP_OK; 422 if (mCallback != null) { 423 Message msg = Message.obtain(mCallback); 424 msg.what = BluetoothMapService.MSG_SESSION_DISCONNECTED; 425 msg.sendToTarget(); 426 if (V) { 427 Log.v(TAG, "onDisconnect(): msg MSG_SESSION_DISCONNECTED sent out."); 428 } 429 } 430 } 431 432 @Override 433 public int onAbort(HeaderSet request, HeaderSet reply) { 434 if (D) { 435 Log.d(TAG, "onAbort(): enter."); 436 } 437 notifyUpdateWakeLock(); 438 mIsAborted = true; 439 return ResponseCodes.OBEX_HTTP_OK; 440 } 441 442 private boolean isUserUnlocked() { 443 UserManager manager = UserManager.get(mContext); 444 return (manager == null || manager.isUserUnlocked()); 445 } 446 447 @Override 448 public int onPut(final Operation op) { 449 if (D) { 450 Log.d(TAG, "onPut(): enter"); 451 } 452 mIsAborted = false; 453 notifyUpdateWakeLock(); 454 HeaderSet request = null; 455 String type, name; 456 byte[] appParamRaw; 457 BluetoothMapAppParams appParams = null; 458 459 try { 460 request = op.getReceivedHeader(); 461 if (V) { 462 logHeader(request); 463 } 464 type = (String) request.getHeader(HeaderSet.TYPE); 465 name = (String) request.getHeader(HeaderSet.NAME); 466 appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER); 467 if (appParamRaw != null) { 468 appParams = new BluetoothMapAppParams(appParamRaw); 469 } 470 if (D) { 471 Log.d(TAG, "type = " + type + ", name = " + name); 472 } 473 if (type.equals(TYPE_MESSAGE_UPDATE)) { 474 if (V) { 475 Log.d(TAG, "TYPE_MESSAGE_UPDATE:"); 476 } 477 return updateInbox(); 478 } else if (type.equals(TYPE_SET_NOTIFICATION_REGISTRATION)) { 479 if (V) { 480 Log.d(TAG, "TYPE_SET_NOTIFICATION_REGISTRATION: NotificationStatus: " 481 + appParams.getNotificationStatus()); 482 } 483 return mObserver.setNotificationRegistration(appParams.getNotificationStatus()); 484 } else if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) { 485 if (V) { 486 Log.d(TAG, "TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: " 487 + appParams.getNotificationFilter()); 488 } 489 if (!isUserUnlocked()) { 490 Log.e(TAG, "Storage locked, " + type + " failed"); 491 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 492 } 493 mObserver.setNotificationFilter(appParams.getNotificationFilter()); 494 return ResponseCodes.OBEX_HTTP_OK; 495 } else if (type.equals(TYPE_SET_MESSAGE_STATUS)) { 496 if (V) { 497 Log.d(TAG, "TYPE_SET_MESSAGE_STATUS: " + "StatusIndicator: " 498 + appParams.getStatusIndicator() + ", StatusValue: " 499 + appParams.getStatusValue() 500 + ", ExtentedData: "); // TODO: appParams.getExtendedImData()); 501 } 502 if (!isUserUnlocked()) { 503 Log.e(TAG, "Storage locked, " + type + " failed"); 504 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 505 } 506 return setMessageStatus(name, appParams); 507 } else if (type.equals(TYPE_MESSAGE)) { 508 if (V) { 509 Log.d(TAG, 510 "TYPE_MESSAGE: Transparet: " + appParams.getTransparent() + ", retry: " 511 + appParams.getRetry() + ", charset: " 512 + appParams.getCharset()); 513 } 514 if (!isUserUnlocked()) { 515 Log.e(TAG, "Storage locked, " + type + " failed"); 516 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 517 } 518 return pushMessage(op, name, appParams, mMessageVersion); 519 } else if (type.equals(TYPE_SET_OWNER_STATUS)) { 520 if (V) { 521 Log.d(TAG, "TYPE_SET_OWNER_STATUS:" + " PresenceAvailability " 522 + appParams.getPresenceAvailability() + ", PresenceStatus: " + appParams 523 .getPresenceStatus() + ", LastActivity: " 524 + appParams.getLastActivityString() + ", ChatStatus: " 525 + appParams.getChatState() + ", ChatStatusConvoId: " 526 + appParams.getChatStateConvoIdString()); 527 } 528 return setOwnerStatus(name, appParams); 529 } 530 531 } catch (RemoteException e) { 532 //reload the providerClient and return error 533 try { 534 mProviderClient = acquireUnstableContentProviderOrThrow(); 535 } catch (RemoteException e2) { 536 //should not happen 537 } 538 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 539 } catch (Exception e) { 540 541 if (D) { 542 Log.e(TAG, "Exception occured while handling request", e); 543 } else { 544 Log.e(TAG, "Exception occured while handling request"); 545 } 546 if (mIsAborted) { 547 return ResponseCodes.OBEX_HTTP_OK; 548 } else { 549 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 550 } 551 } 552 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 553 } 554 555 private int updateInbox() throws RemoteException { 556 if (mAccount != null) { 557 BluetoothMapFolderElement inboxFolder = 558 mCurrentFolder.getFolderByName(BluetoothMapContract.FOLDER_NAME_INBOX); 559 if (inboxFolder != null) { 560 long accountId = mAccountId; 561 if (D) { 562 Log.d(TAG, "updateInbox inbox=" + inboxFolder.getName() + "id=" 563 + inboxFolder.getFolderId()); 564 } 565 566 final Bundle extras = new Bundle(2); 567 if (accountId != -1) { 568 if (D) { 569 Log.d(TAG, "updateInbox accountId=" + accountId); 570 } 571 extras.putLong(BluetoothMapContract.EXTRA_UPDATE_FOLDER_ID, 572 inboxFolder.getFolderId()); 573 extras.putLong(BluetoothMapContract.EXTRA_UPDATE_ACCOUNT_ID, accountId); 574 } else { 575 // Only error code allowed on an UpdateInbox is OBEX_HTTP_NOT_IMPLEMENTED, 576 // i.e. if e.g. update not allowed on the mailbox 577 if (D) { 578 Log.d(TAG, "updateInbox accountId=0 -> OBEX_HTTP_NOT_IMPLEMENTED"); 579 } 580 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 581 } 582 583 Uri emailUri = Uri.parse(mBaseUriString); 584 if (D) { 585 Log.d(TAG, "updateInbox in: " + emailUri.toString()); 586 } 587 try { 588 if (D) { 589 Log.d(TAG, "updateInbox call()..."); 590 } 591 Bundle myBundle = 592 mProviderClient.call(BluetoothMapContract.METHOD_UPDATE_FOLDER, null, 593 extras); 594 if (myBundle != null) { 595 return ResponseCodes.OBEX_HTTP_OK; 596 } else { 597 if (D) { 598 Log.d(TAG, "updateInbox call failed"); 599 } 600 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 601 } 602 } catch (RemoteException e) { 603 mProviderClient = acquireUnstableContentProviderOrThrow(); 604 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 605 } catch (NullPointerException e) { 606 if (D) { 607 Log.e(TAG, "UpdateInbox - if uri or method is null", e); 608 } 609 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 610 611 } catch (IllegalArgumentException e) { 612 if (D) { 613 Log.e(TAG, "UpdateInbox - if uri is not known", e); 614 } 615 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 616 } 617 } 618 } 619 620 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 621 } 622 623 private BluetoothMapFolderElement getFolderElementFromName(String folderName) { 624 BluetoothMapFolderElement folderElement = null; 625 626 if (folderName == null || folderName.trim().isEmpty()) { 627 folderElement = mCurrentFolder; 628 if (D) { 629 Log.d(TAG, "no folder name supplied, setting folder to current: " 630 + folderElement.getName()); 631 } 632 } else { 633 folderElement = mCurrentFolder.getSubFolder(folderName); 634 if (folderElement != null) { 635 if (D) { 636 Log.d(TAG, "Folder name: " + folderName + " resulted in this element: " 637 + folderElement.getName()); 638 } 639 } 640 } 641 return folderElement; 642 } 643 644 private int pushMessage(final Operation op, String folderName, BluetoothMapAppParams appParams, 645 String messageVersion) { 646 if (appParams.getCharset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 647 if (D) { 648 Log.d(TAG, "pushMessage: Missing charset - unable to decode message content. " 649 + "appParams.getCharset() = " + appParams.getCharset()); 650 } 651 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 652 } 653 InputStream bMsgStream = null; 654 try { 655 BluetoothMapFolderElement folderElement = getFolderElementFromName(folderName); 656 if (folderElement == null) { 657 Log.w(TAG, "pushMessage: folderElement == null - sending OBEX_HTTP_PRECON_FAILED"); 658 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 659 } else { 660 folderName = folderElement.getName(); 661 } 662 if (!folderName.equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_OUTBOX) && !folderName 663 .equalsIgnoreCase(BluetoothMapContract.FOLDER_NAME_DRAFT)) { 664 if (D) { 665 Log.d(TAG, "pushMessage: Is only allowed to outbox and draft. " + "folderName=" 666 + folderName); 667 } 668 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 669 } 670 671 /* - Read out the message 672 * - Decode into a bMessage 673 * - send it. 674 */ 675 BluetoothMapbMessage message; 676 bMsgStream = op.openInputStream(); 677 // Decode the messageBody 678 message = BluetoothMapbMessage.parse(bMsgStream, appParams.getCharset()); 679 message.setVersionString(messageVersion); 680 if (D) { 681 Log.d(TAG, "pushMessage: charset" + appParams.getCharset() + "folderId: " 682 + folderElement.getFolderId() + "Name: " + folderName + "TYPE: " 683 + message.getType()); 684 } 685 if (message.getType().equals(TYPE.SMS_GSM) || message.getType().equals(TYPE.SMS_CDMA)) { 686 // Convert messages to the default network type. 687 TelephonyManager tm = 688 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 689 if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { 690 message.setType(TYPE.SMS_GSM); 691 } else if (tm.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 692 message.setType(TYPE.SMS_CDMA); 693 } 694 if (D) { 695 Log.d(TAG, "Updated message type: " + message.getType()); 696 } 697 } 698 // Send message 699 if (mObserver == null || message == null) { 700 // Should not happen except at shutdown. 701 if (D) { 702 Log.w(TAG, "mObserver or parsed message not available"); 703 } 704 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 705 } 706 707 if ((message.getType().equals(TYPE.EMAIL) && (folderElement.getFolderId() == -1)) || ( 708 (message.getType().equals(TYPE.SMS_GSM) || message.getType() 709 .equals(TYPE.SMS_CDMA) || message.getType().equals(TYPE.MMS)) 710 && !folderElement.hasSmsMmsContent())) { 711 if (D) { 712 Log.w(TAG, "Wrong message type recieved"); 713 } 714 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 715 } 716 717 long handle = mObserver.pushMessage(message, folderElement, appParams, mBaseUriString); 718 if (D) { 719 Log.d(TAG, "pushMessage handle: " + handle); 720 } 721 if (handle < 0) { 722 if (D) { 723 Log.w(TAG, "Message handle not created"); 724 } 725 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen. 726 } 727 HeaderSet replyHeaders = new HeaderSet(); 728 String handleStr = BluetoothMapUtils.getMapHandle(handle, message.getType()); 729 if (D) { 730 Log.d(TAG, "handleStr: " + handleStr + " message.getType(): " + message.getType()); 731 } 732 replyHeaders.setHeader(HeaderSet.NAME, handleStr); 733 op.sendHeaders(replyHeaders); 734 735 } catch (RemoteException e) { 736 //reload the providerClient and return error 737 try { 738 mProviderClient = acquireUnstableContentProviderOrThrow(); 739 } catch (RemoteException e2) { 740 //should not happen 741 if (D) { 742 Log.w(TAG, "acquireUnstableContentProviderOrThrow FAILED"); 743 } 744 } 745 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 746 } catch (IllegalArgumentException e) { 747 if (D) { 748 Log.e(TAG, "Wrongly formatted bMessage received", e); 749 } 750 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 751 } catch (IOException e) { 752 if (D) { 753 Log.e(TAG, "Exception occured: ", e); 754 } 755 if (mIsAborted) { 756 if (D) { 757 Log.d(TAG, "PushMessage Operation Aborted"); 758 } 759 return ResponseCodes.OBEX_HTTP_OK; 760 } else { 761 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 762 } 763 } catch (Exception e) { 764 if (D) { 765 Log.e(TAG, "Exception:", e); 766 } 767 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 768 } finally { 769 if (bMsgStream != null) { 770 try { 771 bMsgStream.close(); 772 } catch (IOException e) { 773 } 774 } 775 } 776 return ResponseCodes.OBEX_HTTP_OK; 777 } 778 779 private int setMessageStatus(String msgHandle, BluetoothMapAppParams appParams) { 780 int indicator = appParams.getStatusIndicator(); 781 int value = appParams.getStatusValue(); 782 String extendedData = ""; // TODO: appParams.getExtendedImData(); 783 784 long handle; 785 BluetoothMapUtils.TYPE msgType; 786 787 if (msgHandle == null) { 788 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 789 } else if ((indicator == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 790 || value == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) 791 && extendedData == null) { 792 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 793 } 794 if (mObserver == null) { 795 if (D) { 796 Log.e(TAG, "Error: no mObserver!"); 797 } 798 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; // Should not happen. 799 } 800 801 try { 802 handle = BluetoothMapUtils.getCpHandle(msgHandle); 803 msgType = BluetoothMapUtils.getMsgTypeFromHandle(msgHandle); 804 if (D) { 805 Log.d(TAG, "setMessageStatus. Handle:" + handle + ", MsgType: " + msgType); 806 } 807 } catch (NumberFormatException e) { 808 Log.w(TAG, "Wrongly formatted message handle: " + msgHandle); 809 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 810 } catch (IllegalArgumentException e) { 811 Log.w(TAG, "Message type not found in handle string: " + msgHandle); 812 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 813 } 814 815 if (indicator == BluetoothMapAppParams.STATUS_INDICATOR_DELETED) { 816 if (!mObserver.setMessageStatusDeleted(handle, msgType, mCurrentFolder, mBaseUriString, 817 value)) { 818 if (D) { 819 Log.w(TAG, "setMessageStatusDeleted failed"); 820 } 821 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 822 } 823 } else if (indicator == BluetoothMapAppParams.STATUS_INDICATOR_READ) { 824 try { 825 if (!mObserver.setMessageStatusRead(handle, msgType, mBaseUriString, value)) { 826 if (D) { 827 Log.w(TAG, "not able to update the message"); 828 } 829 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 830 } 831 } catch (RemoteException e) { 832 if (D) { 833 Log.w(TAG, "Error in setMessageStatusRead()", e); 834 } 835 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 836 } 837 } 838 return ResponseCodes.OBEX_HTTP_OK; 839 } 840 841 private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams) 842 throws RemoteException { 843 // This does only work for IM 844 if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) { 845 final Bundle extras = new Bundle(5); 846 847 int presenceState = appParams.getPresenceAvailability(); 848 String presenceStatus = appParams.getPresenceStatus(); 849 long lastActivity = appParams.getLastActivity(); 850 int chatState = appParams.getChatState(); 851 String chatStatusConvoId = appParams.getChatStateConvoIdString(); 852 853 if (presenceState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 854 && presenceStatus == null 855 && lastActivity == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 856 && chatState == BluetoothMapAppParams.INVALID_VALUE_PARAMETER 857 && chatStatusConvoId == null) { 858 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 859 } 860 861 if (presenceState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 862 extras.putInt(BluetoothMapContract.EXTRA_PRESENCE_STATE, presenceState); 863 } 864 if (presenceStatus != null) { 865 extras.putString(BluetoothMapContract.EXTRA_PRESENCE_STATUS, presenceStatus); 866 } 867 if (lastActivity != BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 868 extras.putLong(BluetoothMapContract.EXTRA_LAST_ACTIVE, lastActivity); 869 } 870 if (chatState != BluetoothMapAppParams.INVALID_VALUE_PARAMETER 871 && chatStatusConvoId != null) { 872 extras.putInt(BluetoothMapContract.EXTRA_CHAT_STATE, chatState); 873 extras.putString(BluetoothMapContract.EXTRA_CONVERSATION_ID, chatStatusConvoId); 874 } 875 876 Uri uri = Uri.parse(mBaseUriString); 877 if (D) { 878 Log.d(TAG, "setOwnerStatus in: " + uri.toString()); 879 } 880 try { 881 if (D) { 882 Log.d(TAG, "setOwnerStatus call()..."); 883 } 884 Bundle myBundle = 885 mProviderClient.call(BluetoothMapContract.METHOD_SET_OWNER_STATUS, null, 886 extras); 887 if (myBundle != null) { 888 return ResponseCodes.OBEX_HTTP_OK; 889 } else { 890 if (D) { 891 Log.d(TAG, "setOwnerStatus call failed"); 892 } 893 return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED; 894 } 895 } catch (RemoteException e) { 896 mProviderClient = acquireUnstableContentProviderOrThrow(); 897 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 898 } catch (NullPointerException e) { 899 if (D) { 900 Log.e(TAG, "setOwnerStatus - if uri or method is null", e); 901 } 902 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 903 } catch (IllegalArgumentException e) { 904 if (D) { 905 Log.e(TAG, "setOwnerStatus - if uri is not known", e); 906 } 907 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 908 } 909 } 910 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 911 } 912 913 @Override 914 public int onSetPath(final HeaderSet request, final HeaderSet reply, final boolean backup, 915 final boolean create) { 916 String folderName; 917 BluetoothMapFolderElement folder; 918 notifyUpdateWakeLock(); 919 try { 920 folderName = (String) request.getHeader(HeaderSet.NAME); 921 } catch (Exception e) { 922 if (D) { 923 Log.e(TAG, "request headers error", e); 924 } else { 925 Log.e(TAG, "request headers error"); 926 } 927 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 928 } 929 930 if (V) { 931 logHeader(request); 932 } 933 if (D) { 934 Log.d(TAG, "onSetPath name is " + folderName + " backup: " + backup + " create: " 935 + create); 936 } 937 938 if (backup) { 939 if (mCurrentFolder.getParent() != null) { 940 mCurrentFolder = mCurrentFolder.getParent(); 941 } else { 942 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 943 } 944 } 945 946 if (folderName == null || folderName.trim().isEmpty()) { 947 if (!backup) { 948 mCurrentFolder = mCurrentFolder.getRoot(); 949 } 950 } else { 951 folder = mCurrentFolder.getSubFolder(folderName); 952 if (folder != null) { 953 mCurrentFolder = folder; 954 } else { 955 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 956 } 957 } 958 if (V) { 959 Log.d(TAG, "Current Folder: " + mCurrentFolder.getName()); 960 } 961 return ResponseCodes.OBEX_HTTP_OK; 962 } 963 964 @Override 965 public void onClose() { 966 if (mCallback != null) { 967 Message msg = Message.obtain(mCallback); 968 msg.what = BluetoothMapService.MSG_SERVERSESSION_CLOSE; 969 msg.arg1 = mMasId; 970 msg.sendToTarget(); 971 if (D) { 972 Log.d(TAG, "onClose(): msg MSG_SERVERSESSION_CLOSE sent out."); 973 } 974 975 } 976 if (mProviderClient != null) { 977 mProviderClient.release(); 978 mProviderClient = null; 979 } 980 981 } 982 983 @Override 984 public int onGet(Operation op) { 985 notifyUpdateWakeLock(); 986 mIsAborted = false; 987 HeaderSet request; 988 String type; 989 String name; 990 byte[] appParamRaw = null; 991 BluetoothMapAppParams appParams = null; 992 try { 993 request = op.getReceivedHeader(); 994 type = (String) request.getHeader(HeaderSet.TYPE); 995 996 appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER); 997 if (appParamRaw != null) { 998 appParams = new BluetoothMapAppParams(appParamRaw); 999 } 1000 1001 if (V) { 1002 logHeader(request); 1003 } 1004 if (D) { 1005 Log.d(TAG, "OnGet type is " + type); 1006 } 1007 1008 if (type == null) { 1009 if (V) { 1010 Log.d(TAG, "type is null?" + type); 1011 } 1012 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1013 } 1014 1015 if (type.equals(TYPE_GET_FOLDER_LISTING)) { 1016 if (V && appParams != null) { 1017 Log.d(TAG, 1018 "TYPE_GET_FOLDER_LISTING: MaxListCount = " + appParams.getMaxListCount() 1019 + ", ListStartOffset = " + appParams.getStartOffset()); 1020 } 1021 // Block until all packets have been send. 1022 return sendFolderListingRsp(op, appParams); 1023 } else if (type.equals(TYPE_GET_MESSAGE_LISTING)) { 1024 name = (String) request.getHeader(HeaderSet.NAME); 1025 if (V && appParams != null) { 1026 Log.d(TAG, "TYPE_GET_MESSAGE_LISTING: folder name is: " + name 1027 + ", MaxListCount = " + appParams.getMaxListCount() 1028 + ", ListStartOffset = " + appParams.getStartOffset()); 1029 Log.d(TAG, 1030 "SubjectLength = " + appParams.getSubjectLength() + ", ParameterMask = " 1031 + appParams.getParameterMask()); 1032 Log.d(TAG, "FilterMessageType = " + appParams.getFilterMessageType()); 1033 Log.d(TAG, "FilterPeriodBegin = " + appParams.getFilterPeriodBeginString() 1034 + ", FilterPeriodEnd = " + appParams.getFilterPeriodEndString() 1035 + ", FilterReadStatus = " + appParams.getFilterReadStatus()); 1036 Log.d(TAG, "FilterRecipient = " + appParams.getFilterRecipient() 1037 + ", FilterOriginator = " + appParams.getFilterOriginator()); 1038 Log.d(TAG, "FilterPriority = " + appParams.getFilterPriority()); 1039 long tmpLong = appParams.getFilterMsgHandle(); 1040 Log.d(TAG, "FilterMsgHandle = " + ( 1041 (tmpLong == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) ? "" 1042 : Long.toHexString(tmpLong))); 1043 SignedLongLong tmpLongLong = appParams.getFilterConvoId(); 1044 Log.d(TAG, "FilterConvoId = " + ((tmpLongLong == null) ? "" 1045 : Long.toHexString(tmpLongLong.getLeastSignificantBits()))); 1046 } 1047 if (!isUserUnlocked()) { 1048 Log.e(TAG, "Storage locked, " + type + " failed"); 1049 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 1050 } 1051 // Block until all packets have been send. 1052 return sendMessageListingRsp(op, appParams, name); 1053 1054 } else if (type.equals(TYPE_GET_CONVO_LISTING)) { 1055 name = (String) request.getHeader(HeaderSet.NAME); 1056 if (V && appParams != null) { 1057 Log.d(TAG, "TYPE_GET_CONVO_LISTING: name is" + name + ", MaxListCount = " 1058 + appParams.getMaxListCount() + ", ListStartOffset = " 1059 + appParams.getStartOffset()); 1060 Log.d(TAG, 1061 "FilterLastActivityBegin = " + appParams.getFilterLastActivityBegin()); 1062 Log.d(TAG, "FilterLastActivityEnd = " + appParams.getFilterLastActivityEnd()); 1063 Log.d(TAG, "FilterReadStatus = " + appParams.getFilterReadStatus()); 1064 Log.d(TAG, "FilterRecipient = " + appParams.getFilterRecipient()); 1065 } 1066 if (!isUserUnlocked()) { 1067 Log.e(TAG, "Storage locked, " + type + " failed"); 1068 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 1069 } 1070 // Block until all packets have been send. 1071 return sendConvoListingRsp(op, appParams, name); 1072 } else if (type.equals(TYPE_GET_MAS_INSTANCE_INFORMATION)) { 1073 if (V && appParams != null) { 1074 Log.d(TAG, 1075 "TYPE_MESSAGE (GET): MASInstandeId = " + appParams.getMasInstanceId()); 1076 } 1077 // Block until all packets have been send. 1078 return sendMASInstanceInformationRsp(op, appParams); 1079 } else if (type.equals(TYPE_MESSAGE)) { 1080 name = (String) request.getHeader(HeaderSet.NAME); 1081 if (V && appParams != null) { 1082 Log.d(TAG, "TYPE_MESSAGE (GET): name is" + name + ", Attachment = " 1083 + appParams.getAttachment() + ", Charset = " + appParams.getCharset() 1084 + ", FractionRequest = " + appParams.getFractionRequest()); 1085 } 1086 if (!isUserUnlocked()) { 1087 Log.e(TAG, "Storage locked, " + type + " failed"); 1088 return ResponseCodes.OBEX_HTTP_UNAVAILABLE; 1089 } 1090 // Block until all packets have been send. 1091 return sendGetMessageRsp(op, name, appParams, mMessageVersion); 1092 } else { 1093 Log.w(TAG, "unknown type request: " + type); 1094 return ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE; 1095 } 1096 1097 } catch (IllegalArgumentException e) { 1098 Log.e(TAG, "Exception:", e); 1099 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1100 } catch (ParseException e) { 1101 Log.e(TAG, "Exception:", e); 1102 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1103 } catch (Exception e) { 1104 if (D) { 1105 Log.e(TAG, "Exception occured while handling request", e); 1106 } else { 1107 Log.e(TAG, "Exception occured while handling request"); 1108 } 1109 if (mIsAborted) { 1110 if (D) { 1111 Log.d(TAG, "onGet Operation Aborted"); 1112 } 1113 return ResponseCodes.OBEX_HTTP_OK; 1114 } else { 1115 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1116 } 1117 } 1118 } 1119 1120 /** 1121 * Generate and send the message listing response based on an application 1122 * parameter header. This function call will block until complete or aborted 1123 * by the peer. Fragmentation of packets larger than the obex packet size 1124 * will be handled by this function. 1125 * 1126 * @param op 1127 * The OBEX operation. 1128 * @param appParams 1129 * The application parameter header 1130 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 1131 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 1132 */ 1133 private int sendMessageListingRsp(Operation op, BluetoothMapAppParams appParams, 1134 String folderName) { 1135 OutputStream outStream = null; 1136 byte[] outBytes = null; 1137 int maxChunkSize, bytesToWrite, bytesWritten = 0, listSize; 1138 boolean hasUnread = false; 1139 HeaderSet replyHeaders = new HeaderSet(); 1140 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 1141 BluetoothMapMessageListing outList; 1142 if (appParams == null) { 1143 appParams = new BluetoothMapAppParams(); 1144 appParams.setMaxListCount(1024); 1145 appParams.setStartOffset(0); 1146 } 1147 1148 /* MAP Spec 1.3 introduces the following 1149 * Messagehandle filtering: 1150 * msgListing (messageHandle=X) -> other allowed filters: parametereMask, subjectMaxLength 1151 * ConversationID filtering: 1152 * msgListing (convoId empty) -> should work as normal msgListing in valid folders 1153 * msgListing (convoId=0, no other filters) -> should return all messages in all folders 1154 * msgListing (convoId=N, other filters) -> should return all messages in conversationID=N 1155 * according to filters requested 1156 */ 1157 BluetoothMapFolderElement folderToList = null; 1158 if (appParams.getFilterMsgHandle() != BluetoothMapAppParams.INVALID_VALUE_PARAMETER 1159 || appParams.getFilterConvoId() != null) { 1160 // If messageHandle or convoId filtering ignore folder 1161 Log.v(TAG, "sendMessageListingRsp: ignore folder "); 1162 folderToList = mCurrentFolder.getRoot(); 1163 folderToList.setIngore(true); 1164 } else { 1165 folderToList = getFolderElementFromName(folderName); 1166 if (folderToList == null) { 1167 Log.w(TAG, "sendMessageListingRsp: folderToList == " 1168 + "null-sending OBEX_HTTP_BAD_REQUEST"); 1169 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1170 } 1171 Log.v(TAG, "sendMessageListingRsp: has sms " + folderToList.hasSmsMmsContent() 1172 + ", has email " + folderToList.hasEmailContent() + ", has IM " 1173 + folderToList.hasImContent()); 1174 } 1175 1176 try { 1177 if (appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1178 appParams.setMaxListCount(1024); 1179 } 1180 1181 if (appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1182 appParams.setStartOffset(0); 1183 } 1184 1185 // Check to see if we only need to send the size - hence no need to encode. 1186 if (appParams.getMaxListCount() != 0) { 1187 outList = mOutContent.msgListing(folderToList, appParams); 1188 // Generate the byte stream 1189 outAppParams.setMessageListingSize(outList.getCount()); 1190 String version; 1191 if (0 < (mRemoteFeatureMask 1192 & BluetoothMapUtils.MAP_FEATURE_MESSAGE_LISTING_FORMAT_V11_BIT)) { 1193 version = BluetoothMapUtils.MAP_V11_STR; 1194 } else { 1195 version = BluetoothMapUtils.MAP_V10_STR; 1196 } 1197 /* This will only set the version, the bit must also be checked before adding any 1198 * 1.1 bits to the listing. */ 1199 outBytes = outList.encode(mThreadIdSupport, version); 1200 hasUnread = outList.hasUnread(); 1201 } else { 1202 listSize = mOutContent.msgListingSize(folderToList, appParams); 1203 hasUnread = mOutContent.msgListingHasUnread(folderToList, appParams); 1204 outAppParams.setMessageListingSize(listSize); 1205 op.noBodyHeader(); 1206 } 1207 folderToList.setIngore(false); 1208 // Build the application parameter header 1209 // let the peer know if there are unread messages in the list 1210 if (hasUnread) { 1211 outAppParams.setNewMessage(1); 1212 } else { 1213 outAppParams.setNewMessage(0); 1214 } 1215 if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT) 1216 == BluetoothMapUtils.MAP_FEATURE_DATABASE_INDENTIFIER_BIT) { 1217 outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier()); 1218 } 1219 if ((mRemoteFeatureMask & BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) 1220 == BluetoothMapUtils.MAP_FEATURE_FOLDER_VERSION_COUNTER_BIT) { 1221 // Force update of version counter if needed 1222 mObserver.refreshFolderVersionCounter(); 1223 outAppParams.setFolderVerCounter(mMasInstance.getFolderVersionCounter(), 0); 1224 } 1225 outAppParams.setMseTime(Calendar.getInstance().getTime().getTime()); 1226 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams()); 1227 op.sendHeaders(replyHeaders); 1228 1229 // Open the OBEX body stream 1230 outStream = op.openOutputStream(); 1231 } catch (IOException e) { 1232 Log.w(TAG, "sendMessageListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e); 1233 if (outStream != null) { 1234 try { 1235 outStream.close(); 1236 } catch (IOException ex) { 1237 } 1238 } 1239 if (mIsAborted) { 1240 if (D) { 1241 Log.d(TAG, "sendMessageListingRsp Operation Aborted"); 1242 } 1243 return ResponseCodes.OBEX_HTTP_OK; 1244 } else { 1245 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1246 } 1247 } catch (IllegalArgumentException e) { 1248 Log.w(TAG, "sendMessageListingRsp: IllegalArgumentException" 1249 + " - sending OBEX_HTTP_BAD_REQUEST", e); 1250 if (outStream != null) { 1251 try { 1252 outStream.close(); 1253 } catch (IOException ex) { 1254 } 1255 } 1256 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1257 } 1258 1259 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 1260 if (outBytes != null) { 1261 try { 1262 while (bytesWritten < outBytes.length && !mIsAborted) { 1263 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 1264 outStream.write(outBytes, bytesWritten, bytesToWrite); 1265 bytesWritten += bytesToWrite; 1266 } 1267 } catch (IOException e) { 1268 if (D) { 1269 Log.w(TAG, e); 1270 } 1271 // We were probably aborted or disconnected 1272 } finally { 1273 if (outStream != null) { 1274 try { 1275 outStream.close(); 1276 } catch (IOException e) { 1277 } 1278 } 1279 } 1280 if (bytesWritten != outBytes.length && !mIsAborted) { 1281 Log.w(TAG, "sendMessageListingRsp: bytesWritten != outBytes.length" 1282 + " - sending OBEX_HTTP_BAD_REQUEST"); 1283 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1284 } 1285 } else { 1286 if (outStream != null) { 1287 try { 1288 outStream.close(); 1289 } catch (IOException e) { 1290 } 1291 } 1292 } 1293 return ResponseCodes.OBEX_HTTP_OK; 1294 } 1295 1296 /** 1297 * Update the {@link BluetoothMapAppParams} object message type filter mask to only contain 1298 * message types supported by this mas instance. 1299 * Could the folder be used in stead? 1300 * @param appParams Reference to the object to update 1301 * @param overwrite True: The msgType will be overwritten to match the message types supported 1302 * by this MAS instance. False: any unsupported message types will be masked out. 1303 */ 1304 private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) { 1305 int masFilterMask = 0; 1306 if (!mEnableSmsMms) { 1307 masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA; 1308 masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM; 1309 masFilterMask |= BluetoothMapAppParams.FILTER_NO_MMS; 1310 } 1311 if (mAccount == null) { 1312 masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL; 1313 masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM; 1314 } else { 1315 if (!(mAccount.getType() == BluetoothMapUtils.TYPE.EMAIL)) { 1316 masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL; 1317 } 1318 if (!(mAccount.getType() == BluetoothMapUtils.TYPE.IM)) { 1319 masFilterMask |= BluetoothMapAppParams.FILTER_NO_IM; 1320 } 1321 } 1322 if (overwrite) { 1323 appParams.setFilterMessageType(masFilterMask); 1324 } else { 1325 int newMask = appParams.getFilterMessageType(); 1326 if (newMask == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1327 appParams.setFilterMessageType(newMask); 1328 } else { 1329 newMask |= masFilterMask; 1330 appParams.setFilterMessageType(newMask); 1331 } 1332 } 1333 } 1334 1335 /** 1336 * Generate and send the Conversation listing response based on an application 1337 * parameter header. This function call will block until complete or aborted 1338 * by the peer. Fragmentation of packets larger than the obex packet size 1339 * will be handled by this function. 1340 * 1341 * @param op 1342 * The OBEX operation. 1343 * @param appParams 1344 * The application parameter header 1345 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 1346 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 1347 */ 1348 private int sendConvoListingRsp(Operation op, BluetoothMapAppParams appParams, 1349 String folderName) { 1350 OutputStream outStream = null; 1351 byte[] outBytes = null; 1352 int maxChunkSize, bytesToWrite, bytesWritten = 0; 1353 //boolean hasUnread = false; 1354 HeaderSet replyHeaders = new HeaderSet(); 1355 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 1356 BluetoothMapConvoListing outList; 1357 if (appParams == null) { 1358 appParams = new BluetoothMapAppParams(); 1359 appParams.setMaxListCount(1024); 1360 appParams.setStartOffset(0); 1361 } 1362 // As the app parameters do not carry which message types to list, we set the filter here 1363 // to all message types supported by this instance. 1364 setMsgTypeFilterParams(appParams, true); 1365 1366 // Check to see if we only need to send the size - hence no need to encode. 1367 try { 1368 if (appParams.getMaxListCount() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1369 appParams.setMaxListCount(1024); 1370 } 1371 1372 if (appParams.getStartOffset() == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1373 appParams.setStartOffset(0); 1374 } 1375 1376 if (appParams.getMaxListCount() != 0) { 1377 outList = mOutContent.convoListing(appParams, false); 1378 outAppParams.setConvoListingSize(outList.getCount()); 1379 // Generate the byte stream 1380 outBytes = outList.encode(); // Include thread ID for clients that supports it. 1381 if (D) { 1382 Log.d(TAG, "outBytes size:" + outBytes.length); 1383 } 1384 } else { 1385 outList = mOutContent.convoListing(appParams, true); 1386 outAppParams.setConvoListingSize(outList.getCount()); 1387 if (mEnableSmsMms) { 1388 mOutContent.refreshSmsMmsConvoVersions(); 1389 } 1390 if (mAccount != null) { 1391 mOutContent.refreshImEmailConvoVersions(); 1392 } 1393 // Force update of version counter if needed 1394 mObserver.refreshConvoListVersionCounter(); 1395 if (0 < (mRemoteFeatureMask 1396 & BluetoothMapUtils.MAP_FEATURE_CONVERSATION_VERSION_COUNTER_BIT)) { 1397 outAppParams.setConvoListingVerCounter( 1398 mMasInstance.getCombinedConvoListVersionCounter(), 0); 1399 } 1400 op.noBodyHeader(); 1401 } 1402 if (D) { 1403 Log.d(TAG, "outList size:" + outList.getCount() + " MaxListCount: " 1404 + appParams.getMaxListCount()); 1405 } 1406 outList = null; // We don't need it anymore - we might as well give it up for GC 1407 outAppParams.setDatabaseIdentifier(0, mMasInstance.getDbIdentifier()); 1408 1409 // Build the application parameter header 1410 // The MseTime is not in the CR - but I think it is missing. 1411 outAppParams.setMseTime(Calendar.getInstance().getTime().getTime()); 1412 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams()); 1413 op.sendHeaders(replyHeaders); 1414 1415 // Open the OBEX body stream 1416 outStream = op.openOutputStream(); 1417 } catch (IOException e) { 1418 Log.w(TAG, "sendConvoListingRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e); 1419 if (outStream != null) { 1420 try { 1421 outStream.close(); 1422 } catch (IOException ex) { 1423 } 1424 } 1425 if (mIsAborted) { 1426 if (D) { 1427 Log.d(TAG, "sendConvoListingRsp Operation Aborted"); 1428 } 1429 return ResponseCodes.OBEX_HTTP_OK; 1430 } else { 1431 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1432 } 1433 } catch (IllegalArgumentException e) { 1434 Log.w(TAG, "sendConvoListingRsp: IllegalArgumentException" 1435 + " - sending OBEX_HTTP_BAD_REQUEST", e); 1436 if (outStream != null) { 1437 try { 1438 outStream.close(); 1439 } catch (IOException ex) { 1440 } 1441 } 1442 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1443 } 1444 1445 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 1446 if (outBytes != null) { 1447 try { 1448 while (bytesWritten < outBytes.length && !mIsAborted) { 1449 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 1450 outStream.write(outBytes, bytesWritten, bytesToWrite); 1451 bytesWritten += bytesToWrite; 1452 } 1453 } catch (IOException e) { 1454 if (D) { 1455 Log.w(TAG, e); 1456 } 1457 // We were probably aborted or disconnected 1458 } finally { 1459 if (outStream != null) { 1460 try { 1461 outStream.close(); 1462 } catch (IOException e) { 1463 } 1464 } 1465 } 1466 if (bytesWritten != outBytes.length && !mIsAborted) { 1467 Log.w(TAG, "sendConvoListingRsp: bytesWritten != outBytes.length" 1468 + " - sending OBEX_HTTP_BAD_REQUEST"); 1469 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1470 } 1471 } else { 1472 if (outStream != null) { 1473 try { 1474 outStream.close(); 1475 } catch (IOException e) { 1476 } 1477 } 1478 } 1479 return ResponseCodes.OBEX_HTTP_OK; 1480 } 1481 1482 /** 1483 * Generate and send the Folder listing response based on an application 1484 * parameter header. This function call will block until complete or aborted 1485 * by the peer. Fragmentation of packets larger than the obex packet size 1486 * will be handled by this function. 1487 * 1488 * @param op 1489 * The OBEX operation. 1490 * @param appParams 1491 * The application parameter header 1492 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 1493 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 1494 */ 1495 private int sendFolderListingRsp(Operation op, BluetoothMapAppParams appParams) { 1496 OutputStream outStream = null; 1497 byte[] outBytes = null; 1498 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 1499 int maxChunkSize, bytesWritten = 0; 1500 HeaderSet replyHeaders = new HeaderSet(); 1501 int bytesToWrite, maxListCount, listStartOffset; 1502 if (appParams == null) { 1503 appParams = new BluetoothMapAppParams(); 1504 appParams.setMaxListCount(1024); 1505 } 1506 1507 if (V) { 1508 Log.v(TAG, "sendFolderList for " + mCurrentFolder.getName()); 1509 } 1510 1511 try { 1512 maxListCount = appParams.getMaxListCount(); 1513 listStartOffset = appParams.getStartOffset(); 1514 1515 if (listStartOffset == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1516 listStartOffset = 0; 1517 } 1518 1519 if (maxListCount == BluetoothMapAppParams.INVALID_VALUE_PARAMETER) { 1520 maxListCount = 1024; 1521 } 1522 1523 if (maxListCount != 0) { 1524 outBytes = mCurrentFolder.encode(listStartOffset, maxListCount); 1525 } else { 1526 // ESR08 specified that this shall only be included for MaxListCount=0 1527 outAppParams.setFolderListingSize(mCurrentFolder.getSubFolderCount()); 1528 op.noBodyHeader(); 1529 } 1530 1531 // Build and set the application parameter header 1532 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, outAppParams.encodeParams()); 1533 op.sendHeaders(replyHeaders); 1534 1535 if (maxListCount != 0) { 1536 outStream = op.openOutputStream(); 1537 } 1538 } catch (IOException e1) { 1539 Log.w(TAG, "sendFolderListingRsp: IOException" 1540 + " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1); 1541 if (outStream != null) { 1542 try { 1543 outStream.close(); 1544 } catch (IOException e) { 1545 } 1546 } 1547 if (mIsAborted) { 1548 if (D) { 1549 Log.d(TAG, "sendFolderListingRsp Operation Aborted"); 1550 } 1551 return ResponseCodes.OBEX_HTTP_OK; 1552 } else { 1553 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1554 } 1555 } catch (IllegalArgumentException e1) { 1556 Log.w(TAG, "sendFolderListingRsp: IllegalArgumentException" 1557 + " - sending OBEX_HTTP_BAD_REQUEST Exception:", e1); 1558 if (outStream != null) { 1559 try { 1560 outStream.close(); 1561 } catch (IOException e) { 1562 } 1563 } 1564 return ResponseCodes.OBEX_HTTP_PRECON_FAILED; 1565 } 1566 1567 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 1568 1569 if (outBytes != null) { 1570 try { 1571 while (bytesWritten < outBytes.length && !mIsAborted) { 1572 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 1573 outStream.write(outBytes, bytesWritten, bytesToWrite); 1574 bytesWritten += bytesToWrite; 1575 } 1576 } catch (IOException e) { 1577 // We were probably aborted or disconnected 1578 } finally { 1579 if (outStream != null) { 1580 try { 1581 outStream.close(); 1582 } catch (IOException e) { 1583 } 1584 } 1585 } 1586 if (V) { 1587 Log.v(TAG, 1588 "sendFolderList sent " + bytesWritten + " bytes out of " + outBytes.length); 1589 } 1590 if (bytesWritten == outBytes.length || mIsAborted) { 1591 return ResponseCodes.OBEX_HTTP_OK; 1592 } else { 1593 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1594 } 1595 } 1596 1597 return ResponseCodes.OBEX_HTTP_OK; 1598 } 1599 1600 /** 1601 * Generate and send the get MAS Instance Information response based on an MAS Instance 1602 * 1603 * @param op 1604 * The OBEX operation. 1605 * @param appParams 1606 * The application parameter header 1607 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 1608 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 1609 */ 1610 private int sendMASInstanceInformationRsp(Operation op, BluetoothMapAppParams appParams) { 1611 1612 OutputStream outStream = null; 1613 byte[] outBytes = null; 1614 String outString = null; 1615 int maxChunkSize, bytesToWrite, bytesWritten = 0; 1616 1617 try { 1618 if (mMasId == appParams.getMasInstanceId()) { 1619 if (mAccount != null) { 1620 if (mAccount.getType() == TYPE.EMAIL) { 1621 outString = (mAccount.getName() != null) ? mAccount.getName() 1622 : BluetoothMapMasInstance.TYPE_EMAIL_STR; 1623 } else if (mAccount.getType() == TYPE.IM) { 1624 outString = mAccount.getUciFull(); 1625 if (outString == null) { 1626 String uci = mAccount.getUci(); 1627 // TODO: Do we need to align this with HF/PBAP 1628 StringBuilder sb = 1629 new StringBuilder(uci == null ? 5 : 5 + uci.length()); 1630 sb.append("un"); 1631 if (mMasId < 10) { 1632 sb.append("00"); 1633 } else if (mMasId < 100) { 1634 sb.append("0"); 1635 } 1636 sb.append(mMasId); 1637 if (uci != null) { 1638 sb.append(":").append(uci); 1639 } 1640 outString = sb.toString(); 1641 } 1642 } 1643 } else { 1644 outString = BluetoothMapMasInstance.TYPE_SMS_MMS_STR; 1645 // TODO: Add phone number if possible 1646 } 1647 } else { 1648 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1649 } 1650 1651 /* Ensure byte array max length is 200 containing valid UTF-8 characters */ 1652 outBytes = BluetoothMapUtils.truncateUtf8StringToBytearray(outString, 1653 MAS_INSTANCE_INFORMATION_LENGTH); 1654 1655 // Open the OBEX body stream 1656 outStream = op.openOutputStream(); 1657 1658 } catch (IOException e) { 1659 Log.w(TAG, "sendMASInstanceInformationRsp: IOException" 1660 + " - sending OBEX_HTTP_BAD_REQUEST", e); 1661 if (mIsAborted) { 1662 if (D) { 1663 Log.d(TAG, "sendMASInstanceInformationRsp Operation Aborted"); 1664 } 1665 return ResponseCodes.OBEX_HTTP_OK; 1666 } else { 1667 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1668 } 1669 } 1670 1671 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 1672 1673 if (outBytes != null) { 1674 try { 1675 while (bytesWritten < outBytes.length && !mIsAborted) { 1676 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 1677 outStream.write(outBytes, bytesWritten, bytesToWrite); 1678 bytesWritten += bytesToWrite; 1679 } 1680 } catch (IOException e) { 1681 // We were probably aborted or disconnected 1682 } finally { 1683 if (outStream != null) { 1684 try { 1685 outStream.close(); 1686 } catch (IOException e) { 1687 } 1688 } 1689 } 1690 if (V) { 1691 Log.v(TAG, "sendMASInstanceInformationRsp sent " + bytesWritten + " bytes out of " 1692 + outBytes.length); 1693 } 1694 if (bytesWritten == outBytes.length || mIsAborted) { 1695 return ResponseCodes.OBEX_HTTP_OK; 1696 } else { 1697 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1698 } 1699 } 1700 return ResponseCodes.OBEX_HTTP_OK; 1701 } 1702 1703 /** 1704 * Generate and send the get message response based on an application 1705 * parameter header and a handle. 1706 * 1707 * @param op 1708 * The OBEX operation. 1709 * @param handle 1710 * The handle of the requested message 1711 * @param appParams 1712 * The application parameter header 1713 * @param version 1714 * The string representation of the version number(i.e. "1.0") 1715 * @return {@link ResponseCodes.OBEX_HTTP_OK} on success or 1716 * {@link ResponseCodes.OBEX_HTTP_BAD_REQUEST} on error. 1717 */ 1718 private int sendGetMessageRsp(Operation op, String handle, BluetoothMapAppParams appParams, 1719 String version) { 1720 OutputStream outStream = null; 1721 byte[] outBytes = null; 1722 int maxChunkSize, bytesToWrite, bytesWritten = 0; 1723 1724 try { 1725 outBytes = mOutContent.getMessage(handle, appParams, mCurrentFolder, version); 1726 1727 // If it is a fraction request of Email message, set header before responding 1728 if ((BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.EMAIL) 1729 || (BluetoothMapUtils.getMsgTypeFromHandle(handle).equals(TYPE.IM))) && ( 1730 appParams.getFractionRequest() 1731 == BluetoothMapAppParams.FRACTION_REQUEST_FIRST)) { 1732 BluetoothMapAppParams outAppParams = new BluetoothMapAppParams(); 1733 HeaderSet replyHeaders = new HeaderSet(); 1734 outAppParams.setFractionDeliver(BluetoothMapAppParams.FRACTION_DELIVER_LAST); 1735 // Build and set the application parameter header 1736 replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER, 1737 outAppParams.encodeParams()); 1738 op.sendHeaders(replyHeaders); 1739 if (V) { 1740 Log.v(TAG, "sendGetMessageRsp fractionRequest - " 1741 + "set FRACTION_DELIVER_LAST header"); 1742 } 1743 } 1744 outStream = op.openOutputStream(); 1745 1746 } catch (IOException e) { 1747 Log.w(TAG, "sendGetMessageRsp: IOException - sending OBEX_HTTP_BAD_REQUEST", e); 1748 if (outStream != null) { 1749 try { 1750 outStream.close(); 1751 } catch (IOException ex) { 1752 } 1753 } 1754 if (mIsAborted) { 1755 if (D) { 1756 Log.d(TAG, "sendGetMessageRsp Operation Aborted"); 1757 } 1758 return ResponseCodes.OBEX_HTTP_OK; 1759 } else { 1760 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1761 } 1762 } catch (IllegalArgumentException e) { 1763 Log.w(TAG, "sendGetMessageRsp: IllegalArgumentException (e.g. invalid handle) - " 1764 + "sending OBEX_HTTP_BAD_REQUEST", e); 1765 if (outStream != null) { 1766 try { 1767 outStream.close(); 1768 } catch (IOException ex) { 1769 } 1770 } 1771 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1772 } 1773 1774 maxChunkSize = op.getMaxPacketSize(); // This must be called after setting the headers. 1775 1776 if (outBytes != null) { 1777 try { 1778 while (bytesWritten < outBytes.length && !mIsAborted) { 1779 bytesToWrite = Math.min(maxChunkSize, outBytes.length - bytesWritten); 1780 outStream.write(outBytes, bytesWritten, bytesToWrite); 1781 bytesWritten += bytesToWrite; 1782 } 1783 } catch (IOException e) { 1784 // We were probably aborted or disconnected 1785 if (D && e.getMessage().equals("Abort Received")) { 1786 Log.w(TAG, "getMessage() Aborted...", e); 1787 } 1788 } finally { 1789 if (outStream != null) { 1790 try { 1791 outStream.close(); 1792 } catch (IOException e) { 1793 } 1794 } 1795 } 1796 if (bytesWritten == outBytes.length || mIsAborted) { 1797 return ResponseCodes.OBEX_HTTP_OK; 1798 } else { 1799 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1800 } 1801 } 1802 1803 return ResponseCodes.OBEX_HTTP_OK; 1804 } 1805 1806 @Override 1807 public int onDelete(HeaderSet request, HeaderSet reply) { 1808 if (D) { 1809 Log.v(TAG, "onDelete() " + request.toString()); 1810 } 1811 mIsAborted = false; 1812 notifyUpdateWakeLock(); 1813 String type, name; 1814 byte[] appParamRaw; 1815 BluetoothMapAppParams appParams = null; 1816 1817 /* TODO: If this is to be placed here, we need to cleanup - e.g. the exception handling */ 1818 try { 1819 type = (String) request.getHeader(HeaderSet.TYPE); 1820 1821 name = (String) request.getHeader(HeaderSet.NAME); 1822 appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER); 1823 if (appParamRaw != null) { 1824 appParams = new BluetoothMapAppParams(appParamRaw); 1825 } 1826 if (D) { 1827 Log.d(TAG, "type = " + type + ", name = " + name); 1828 } 1829 if (type.equals(TYPE_SET_NOTIFICATION_FILTER)) { 1830 if (V) { 1831 Log.d(TAG, "TYPE_SET_NOTIFICATION_FILTER: NotificationFilter: " 1832 + appParams.getNotificationFilter()); 1833 } 1834 mObserver.setNotificationFilter(appParams.getNotificationFilter()); 1835 return ResponseCodes.OBEX_HTTP_OK; 1836 } else if (type.equals(TYPE_SET_OWNER_STATUS)) { 1837 if (V) { 1838 Log.d(TAG, "TYPE_SET_OWNER_STATUS:" + " PresenceAvailability " 1839 + appParams.getPresenceAvailability() + ", PresenceStatus: " + appParams 1840 .getPresenceStatus() + ", LastActivity: " 1841 + appParams.getLastActivityString() + ", ChatStatus: " 1842 + appParams.getChatState() + ", ChatStatusConvoId: " 1843 + appParams.getChatStateConvoIdString()); 1844 } 1845 return setOwnerStatus(name, appParams); 1846 } 1847 1848 } catch (RemoteException e) { 1849 //reload the providerClient and return error 1850 try { 1851 mProviderClient = acquireUnstableContentProviderOrThrow(); 1852 } catch (RemoteException e2) { 1853 //should not happen 1854 } 1855 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1856 } catch (Exception e) { 1857 1858 if (D) { 1859 Log.e(TAG, "Exception occured while handling request", e); 1860 } else { 1861 Log.e(TAG, "Exception occured while handling request"); 1862 } 1863 if (mIsAborted) { 1864 return ResponseCodes.OBEX_HTTP_OK; 1865 } else { 1866 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1867 } 1868 } 1869 return ResponseCodes.OBEX_HTTP_BAD_REQUEST; 1870 } 1871 1872 private void notifyUpdateWakeLock() { 1873 if (mCallback != null) { 1874 Message msg = Message.obtain(mCallback); 1875 msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK; 1876 msg.sendToTarget(); 1877 } 1878 } 1879 1880 private static void logHeader(HeaderSet hs) { 1881 Log.v(TAG, "Dumping HeaderSet " + hs.toString()); 1882 try { 1883 Log.v(TAG, "CONNECTION_ID : " + hs.getHeader(HeaderSet.CONNECTION_ID)); 1884 Log.v(TAG, "NAME : " + hs.getHeader(HeaderSet.NAME)); 1885 Log.v(TAG, "TYPE : " + hs.getHeader(HeaderSet.TYPE)); 1886 Log.v(TAG, "TARGET : " + hs.getHeader(HeaderSet.TARGET)); 1887 Log.v(TAG, "WHO : " + hs.getHeader(HeaderSet.WHO)); 1888 Log.v(TAG, "APPLICATION_PARAMETER : " + hs.getHeader(HeaderSet.APPLICATION_PARAMETER)); 1889 } catch (IOException e) { 1890 Log.e(TAG, "dump HeaderSet error " + e); 1891 } 1892 Log.v(TAG, "NEW!!! Dumping HeaderSet END"); 1893 } 1894} 1895