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