1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth.client.map; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothMasInstance; 21 import android.bluetooth.BluetoothSocket; 22 import android.bluetooth.SdpMasRecord; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.util.Log; 26 27 import android.bluetooth.client.map.BluetoothMasRequestSetMessageStatus.StatusIndicator; 28 import android.bluetooth.client.map.utils.ObexTime; 29 30 import java.io.IOException; 31 import java.lang.ref.WeakReference; 32 import java.math.BigInteger; 33 import java.util.ArrayDeque; 34 import java.util.ArrayList; 35 import java.util.Date; 36 import java.util.Iterator; 37 38 import javax.obex.ObexTransport; 39 40 public class BluetoothMasClient { 41 42 private final static String TAG = "BluetoothMasClient"; 43 44 private static final int SOCKET_CONNECTED = 10; 45 46 private static final int SOCKET_ERROR = 11; 47 48 /** 49 * Callback message sent when connection state changes 50 * <p> 51 * <code>arg1</code> is set to {@link #STATUS_OK} when connection is 52 * established successfully and {@link #STATUS_FAILED} when connection 53 * either failed or was disconnected (depends on request from application) 54 * 55 * @see #connect() 56 * @see #disconnect() 57 */ 58 public static final int EVENT_CONNECT = 1; 59 60 /** 61 * Callback message sent when MSE accepted update inbox request 62 * 63 * @see #updateInbox() 64 */ 65 public static final int EVENT_UPDATE_INBOX = 2; 66 67 /** 68 * Callback message sent when path is changed 69 * <p> 70 * <code>obj</code> is set to path currently set on MSE 71 * 72 * @see #setFolderRoot() 73 * @see #setFolderUp() 74 * @see #setFolderDown(String) 75 */ 76 public static final int EVENT_SET_PATH = 3; 77 78 /** 79 * Callback message sent when folder listing is received 80 * <p> 81 * <code>obj</code> contains ArrayList of sub-folder names 82 * 83 * @see #getFolderListing() 84 * @see #getFolderListing(int, int) 85 */ 86 public static final int EVENT_GET_FOLDER_LISTING = 4; 87 88 /** 89 * Callback message sent when folder listing size is received 90 * <p> 91 * <code>obj</code> contains number of items in folder listing 92 * 93 * @see #getFolderListingSize() 94 */ 95 public static final int EVENT_GET_FOLDER_LISTING_SIZE = 5; 96 97 /** 98 * Callback message sent when messages listing is received 99 * <p> 100 * <code>obj</code> contains ArrayList of {@link BluetoothMapBmessage} 101 * 102 * @see #getMessagesListing(String, int) 103 * @see #getMessagesListing(String, int, MessagesFilter, int) 104 * @see #getMessagesListing(String, int, MessagesFilter, int, int, int) 105 */ 106 public static final int EVENT_GET_MESSAGES_LISTING = 6; 107 108 /** 109 * Callback message sent when message is received 110 * <p> 111 * <code>obj</code> contains {@link BluetoothMapBmessage} 112 * 113 * @see #getMessage(String, CharsetType, boolean) 114 */ 115 public static final int EVENT_GET_MESSAGE = 7; 116 117 /** 118 * Callback message sent when message status is changed 119 * 120 * @see #setMessageDeletedStatus(String, boolean) 121 * @see #setMessageReadStatus(String, boolean) 122 */ 123 public static final int EVENT_SET_MESSAGE_STATUS = 8; 124 125 /** 126 * Callback message sent when message is pushed to MSE 127 * <p> 128 * <code>obj</code> contains handle of message as allocated by MSE 129 * 130 * @see #pushMessage(String, BluetoothMapBmessage, CharsetType) 131 * @see #pushMessage(String, BluetoothMapBmessage, CharsetType, boolean, 132 * boolean) 133 */ 134 public static final int EVENT_PUSH_MESSAGE = 9; 135 136 /** 137 * Callback message sent when notification status is changed 138 * <p> 139 * <code>obj</code> contains <code>1</code> if notifications are enabled and 140 * <code>0</code> otherwise 141 * 142 * @see #setNotificationRegistration(boolean) 143 */ 144 public static final int EVENT_SET_NOTIFICATION_REGISTRATION = 10; 145 146 /** 147 * Callback message sent when event report is received from MSE to MNS 148 * <p> 149 * <code>obj</code> contains {@link BluetoothMapEventReport} 150 * 151 * @see #setNotificationRegistration(boolean) 152 */ 153 public static final int EVENT_EVENT_REPORT = 11; 154 155 /** 156 * Callback message sent when messages listing size is received 157 * <p> 158 * <code>obj</code> contains number of items in messages listing 159 * 160 * @see #getMessagesListingSize() 161 */ 162 public static final int EVENT_GET_MESSAGES_LISTING_SIZE = 12; 163 164 /** 165 * Status for callback message when request is successful 166 */ 167 public static final int STATUS_OK = 0; 168 169 /** 170 * Status for callback message when request is not successful 171 */ 172 public static final int STATUS_FAILED = 1; 173 174 /** 175 * Constant corresponding to <code>ParameterMask</code> application 176 * parameter value in MAP specification 177 */ 178 public static final int PARAMETER_DEFAULT = 0x00000000; 179 180 /** 181 * Constant corresponding to <code>ParameterMask</code> application 182 * parameter value in MAP specification 183 */ 184 public static final int PARAMETER_SUBJECT = 0x00000001; 185 186 /** 187 * Constant corresponding to <code>ParameterMask</code> application 188 * parameter value in MAP specification 189 */ 190 public static final int PARAMETER_DATETIME = 0x00000002; 191 192 /** 193 * Constant corresponding to <code>ParameterMask</code> application 194 * parameter value in MAP specification 195 */ 196 public static final int PARAMETER_SENDER_NAME = 0x00000004; 197 198 /** 199 * Constant corresponding to <code>ParameterMask</code> application 200 * parameter value in MAP specification 201 */ 202 public static final int PARAMETER_SENDER_ADDRESSING = 0x00000008; 203 204 /** 205 * Constant corresponding to <code>ParameterMask</code> application 206 * parameter value in MAP specification 207 */ 208 public static final int PARAMETER_RECIPIENT_NAME = 0x00000010; 209 210 /** 211 * Constant corresponding to <code>ParameterMask</code> application 212 * parameter value in MAP specification 213 */ 214 public static final int PARAMETER_RECIPIENT_ADDRESSING = 0x00000020; 215 216 /** 217 * Constant corresponding to <code>ParameterMask</code> application 218 * parameter value in MAP specification 219 */ 220 public static final int PARAMETER_TYPE = 0x00000040; 221 222 /** 223 * Constant corresponding to <code>ParameterMask</code> application 224 * parameter value in MAP specification 225 */ 226 public static final int PARAMETER_SIZE = 0x00000080; 227 228 /** 229 * Constant corresponding to <code>ParameterMask</code> application 230 * parameter value in MAP specification 231 */ 232 public static final int PARAMETER_RECEPTION_STATUS = 0x00000100; 233 234 /** 235 * Constant corresponding to <code>ParameterMask</code> application 236 * parameter value in MAP specification 237 */ 238 public static final int PARAMETER_TEXT = 0x00000200; 239 240 /** 241 * Constant corresponding to <code>ParameterMask</code> application 242 * parameter value in MAP specification 243 */ 244 public static final int PARAMETER_ATTACHMENT_SIZE = 0x00000400; 245 246 /** 247 * Constant corresponding to <code>ParameterMask</code> application 248 * parameter value in MAP specification 249 */ 250 public static final int PARAMETER_PRIORITY = 0x00000800; 251 252 /** 253 * Constant corresponding to <code>ParameterMask</code> application 254 * parameter value in MAP specification 255 */ 256 public static final int PARAMETER_READ = 0x00001000; 257 258 /** 259 * Constant corresponding to <code>ParameterMask</code> application 260 * parameter value in MAP specification 261 */ 262 public static final int PARAMETER_SENT = 0x00002000; 263 264 /** 265 * Constant corresponding to <code>ParameterMask</code> application 266 * parameter value in MAP specification 267 */ 268 public static final int PARAMETER_PROTECTED = 0x00004000; 269 270 /** 271 * Constant corresponding to <code>ParameterMask</code> application 272 * parameter value in MAP specification 273 */ 274 public static final int PARAMETER_REPLYTO_ADDRESSING = 0x00008000; 275 276 public enum ConnectionState { 277 DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING; 278 } 279 280 public enum CharsetType { 281 NATIVE, UTF_8; 282 } 283 284 /** device associated with client */ 285 private final BluetoothDevice mDevice; 286 287 /** MAS instance associated with client */ 288 private final SdpMasRecord mMas; 289 290 /** callback handler to application */ 291 private final Handler mCallback; 292 293 private ConnectionState mConnectionState = ConnectionState.DISCONNECTED; 294 295 private boolean mNotificationEnabled = false; 296 297 private SocketConnectThread mConnectThread = null; 298 299 private ObexTransport mObexTransport = null; 300 301 private BluetoothMasObexClientSession mObexSession = null; 302 303 private SessionHandler mSessionHandler = null; 304 305 private BluetoothMnsService mMnsService = null; 306 307 private ArrayDeque<String> mPath = null; 308 309 private static class SessionHandler extends Handler { 310 311 private final WeakReference<BluetoothMasClient> mClient; 312 SessionHandler(BluetoothMasClient client)313 public SessionHandler(BluetoothMasClient client) { 314 super(); 315 316 mClient = new WeakReference<BluetoothMasClient>(client); 317 } 318 319 @Override handleMessage(Message msg)320 public void handleMessage(Message msg) { 321 322 BluetoothMasClient client = mClient.get(); 323 if (client == null) { 324 return; 325 } 326 Log.v(TAG, "handleMessage "+msg.what); 327 328 switch (msg.what) { 329 case SOCKET_ERROR: 330 client.mConnectThread = null; 331 client.sendToClient(EVENT_CONNECT, false); 332 break; 333 334 case SOCKET_CONNECTED: 335 client.mConnectThread = null; 336 337 client.mObexTransport = (ObexTransport) msg.obj; 338 339 client.mObexSession = new BluetoothMasObexClientSession(client.mObexTransport, 340 client.mSessionHandler); 341 client.mObexSession.start(); 342 break; 343 344 case BluetoothMasObexClientSession.MSG_OBEX_CONNECTED: 345 client.mPath.clear(); // we're in root after connected 346 client.mConnectionState = ConnectionState.CONNECTED; 347 client.sendToClient(EVENT_CONNECT, true); 348 break; 349 350 case BluetoothMasObexClientSession.MSG_OBEX_DISCONNECTED: 351 client.mConnectionState = ConnectionState.DISCONNECTED; 352 client.mNotificationEnabled = false; 353 client.mObexSession = null; 354 client.sendToClient(EVENT_CONNECT, false); 355 break; 356 357 case BluetoothMasObexClientSession.MSG_REQUEST_COMPLETED: 358 BluetoothMasRequest request = (BluetoothMasRequest) msg.obj; 359 int status = request.isSuccess() ? STATUS_OK : STATUS_FAILED; 360 361 Log.v(TAG, "MSG_REQUEST_COMPLETED (" + status + ") for " 362 + request.getClass().getName()); 363 364 if (request instanceof BluetoothMasRequestUpdateInbox) { 365 client.sendToClient(EVENT_UPDATE_INBOX, request.isSuccess()); 366 367 } else if (request instanceof BluetoothMasRequestSetPath) { 368 if (request.isSuccess()) { 369 BluetoothMasRequestSetPath req = (BluetoothMasRequestSetPath) request; 370 switch (req.mDir) { 371 case UP: 372 if (client.mPath.size() > 0) { 373 client.mPath.removeLast(); 374 } 375 break; 376 377 case ROOT: 378 client.mPath.clear(); 379 break; 380 381 case DOWN: 382 client.mPath.addLast(req.mName); 383 break; 384 } 385 } 386 387 client.sendToClient(EVENT_SET_PATH, request.isSuccess(), 388 client.getCurrentPath()); 389 390 } else if (request instanceof BluetoothMasRequestGetFolderListing) { 391 BluetoothMasRequestGetFolderListing req = (BluetoothMasRequestGetFolderListing) request; 392 ArrayList<String> folders = req.getList(); 393 394 client.sendToClient(EVENT_GET_FOLDER_LISTING, request.isSuccess(), folders); 395 396 } else if (request instanceof BluetoothMasRequestGetFolderListingSize) { 397 int size = ((BluetoothMasRequestGetFolderListingSize) request).getSize(); 398 399 client.sendToClient(EVENT_GET_FOLDER_LISTING_SIZE, request.isSuccess(), 400 size); 401 402 } else if (request instanceof BluetoothMasRequestGetMessagesListing) { 403 BluetoothMasRequestGetMessagesListing req = (BluetoothMasRequestGetMessagesListing) request; 404 ArrayList<BluetoothMapMessage> msgs = req.getList(); 405 406 client.sendToClient(EVENT_GET_MESSAGES_LISTING, request.isSuccess(), msgs); 407 408 } else if (request instanceof BluetoothMasRequestGetMessage) { 409 BluetoothMasRequestGetMessage req = (BluetoothMasRequestGetMessage) request; 410 BluetoothMapBmessage bmsg = req.getMessage(); 411 412 client.sendToClient(EVENT_GET_MESSAGE, request.isSuccess(), bmsg); 413 414 } else if (request instanceof BluetoothMasRequestSetMessageStatus) { 415 client.sendToClient(EVENT_SET_MESSAGE_STATUS, request.isSuccess()); 416 417 } else if (request instanceof BluetoothMasRequestPushMessage) { 418 BluetoothMasRequestPushMessage req = (BluetoothMasRequestPushMessage) request; 419 String handle = req.getMsgHandle(); 420 421 client.sendToClient(EVENT_PUSH_MESSAGE, request.isSuccess(), handle); 422 423 } else if (request instanceof BluetoothMasRequestSetNotificationRegistration) { 424 BluetoothMasRequestSetNotificationRegistration req = (BluetoothMasRequestSetNotificationRegistration) request; 425 426 client.mNotificationEnabled = req.isSuccess() ? req.getStatus() 427 : client.mNotificationEnabled; 428 429 client.sendToClient(EVENT_SET_NOTIFICATION_REGISTRATION, 430 request.isSuccess(), 431 client.mNotificationEnabled ? 1 : 0); 432 } else if (request instanceof BluetoothMasRequestGetMessagesListingSize) { 433 int size = ((BluetoothMasRequestGetMessagesListingSize) request).getSize(); 434 client.sendToClient(EVENT_GET_MESSAGES_LISTING_SIZE, request.isSuccess(), 435 size); 436 } 437 break; 438 439 case BluetoothMnsService.EVENT_REPORT: 440 /* pass event report directly to app */ 441 client.sendToClient(EVENT_EVENT_REPORT, true, msg.obj); 442 break; 443 } 444 } 445 } 446 sendToClient(int event, boolean success)447 private void sendToClient(int event, boolean success) { 448 sendToClient(event, success, null); 449 } 450 sendToClient(int event, boolean success, int param)451 private void sendToClient(int event, boolean success, int param) { 452 sendToClient(event, success, Integer.valueOf(param)); 453 } 454 sendToClient(int event, boolean success, Object param)455 private void sendToClient(int event, boolean success, Object param) { 456 // Send event, status and notification state for both sucess and failure case. 457 mCallback.obtainMessage(event, success ? STATUS_OK : STATUS_FAILED, mMas.getMasInstanceId(), 458 param).sendToTarget(); 459 } 460 461 private class SocketConnectThread extends Thread { 462 private BluetoothSocket socket = null; 463 SocketConnectThread()464 public SocketConnectThread() { 465 super("SocketConnectThread"); 466 } 467 468 @Override run()469 public void run() { 470 try { 471 socket = mDevice.createRfcommSocket(mMas.getRfcommCannelNumber()); 472 socket.connect(); 473 474 BluetoothMapRfcommTransport transport; 475 transport = new BluetoothMapRfcommTransport(socket); 476 477 mSessionHandler.obtainMessage(SOCKET_CONNECTED, transport).sendToTarget(); 478 } catch (IOException e) { 479 Log.e(TAG, "Error when creating/connecting socket", e); 480 481 closeSocket(); 482 mSessionHandler.obtainMessage(SOCKET_ERROR).sendToTarget(); 483 } 484 } 485 486 @Override interrupt()487 public void interrupt() { 488 closeSocket(); 489 } 490 closeSocket()491 private void closeSocket() { 492 try { 493 if (socket != null) { 494 socket.close(); 495 } 496 } catch (IOException e) { 497 Log.e(TAG, "Error when closing socket", e); 498 } 499 } 500 } 501 502 /** 503 * Object representation of filters to be applied on message listing 504 * 505 * @see #getMessagesListing(String, int, MessagesFilter, int) 506 * @see #getMessagesListing(String, int, MessagesFilter, int, int, int) 507 */ 508 public static final class MessagesFilter { 509 510 public final static byte MESSAGE_TYPE_ALL = 0x00; 511 public final static byte MESSAGE_TYPE_SMS_GSM = 0x01; 512 public final static byte MESSAGE_TYPE_SMS_CDMA = 0x02; 513 public final static byte MESSAGE_TYPE_EMAIL = 0x04; 514 public final static byte MESSAGE_TYPE_MMS = 0x08; 515 516 public final static byte READ_STATUS_ANY = 0x00; 517 public final static byte READ_STATUS_UNREAD = 0x01; 518 public final static byte READ_STATUS_READ = 0x02; 519 520 public final static byte PRIORITY_ANY = 0x00; 521 public final static byte PRIORITY_HIGH = 0x01; 522 public final static byte PRIORITY_NON_HIGH = 0x02; 523 524 byte messageType = MESSAGE_TYPE_ALL; 525 526 String periodBegin = null; 527 528 String periodEnd = null; 529 530 byte readStatus = READ_STATUS_ANY; 531 532 String recipient = null; 533 534 String originator = null; 535 536 byte priority = PRIORITY_ANY; 537 MessagesFilter()538 public MessagesFilter() { 539 } 540 setMessageType(byte filter)541 public void setMessageType(byte filter) { 542 messageType = filter; 543 } 544 setPeriod(Date filterBegin, Date filterEnd)545 public void setPeriod(Date filterBegin, Date filterEnd) { 546 //Handle possible NPE for obexTime constructor utility 547 if(filterBegin != null ) 548 periodBegin = (new ObexTime(filterBegin)).toString(); 549 if(filterEnd != null) 550 periodEnd = (new ObexTime(filterEnd)).toString(); 551 } 552 setReadStatus(byte readfilter)553 public void setReadStatus(byte readfilter) { 554 readStatus = readfilter; 555 } 556 setRecipient(String filter)557 public void setRecipient(String filter) { 558 if ("".equals(filter)) { 559 recipient = null; 560 } else { 561 recipient = filter; 562 } 563 } 564 setOriginator(String filter)565 public void setOriginator(String filter) { 566 if ("".equals(filter)) { 567 originator = null; 568 } else { 569 originator = filter; 570 } 571 } 572 setPriority(byte filter)573 public void setPriority(byte filter) { 574 priority = filter; 575 } 576 } 577 578 /** 579 * Constructs client object to communicate with single MAS instance on MSE 580 * 581 * @param device {@link BluetoothDevice} corresponding to remote device 582 * acting as MSE 583 * @param mas {@link BluetoothMasInstance} object describing MAS instance on 584 * remote device 585 * @param callback {@link Handler} object to which callback messages will be 586 * sent Each message will have <code>arg1</code> set to either 587 * {@link #STATUS_OK} or {@link #STATUS_FAILED} and 588 * <code>arg2</code> to MAS instance ID. <code>obj</code> in 589 * message is event specific. 590 */ BluetoothMasClient(BluetoothDevice device, SdpMasRecord mas, Handler callback)591 public BluetoothMasClient(BluetoothDevice device, SdpMasRecord mas, 592 Handler callback) { 593 mDevice = device; 594 mMas = mas; 595 mCallback = callback; 596 597 mPath = new ArrayDeque<String>(); 598 } 599 600 /** 601 * Retrieves MAS instance data associated with client 602 * 603 * @return instance data object 604 */ getInstanceData()605 public SdpMasRecord getInstanceData() { 606 return mMas; 607 } 608 609 /** 610 * Connects to MAS instance 611 * <p> 612 * Upon completion callback handler will receive {@link #EVENT_CONNECT} 613 */ connect()614 public void connect() { 615 if (mSessionHandler == null) { 616 mSessionHandler = new SessionHandler(this); 617 } 618 619 if (mConnectThread == null && mObexSession == null) { 620 mConnectionState = ConnectionState.CONNECTING; 621 622 mConnectThread = new SocketConnectThread(); 623 mConnectThread.start(); 624 } 625 } 626 627 /** 628 * Disconnects from MAS instance 629 * <p> 630 * Upon completion callback handler will receive {@link #EVENT_CONNECT} 631 */ disconnect()632 public void disconnect() { 633 if (mConnectThread == null && mObexSession == null) { 634 return; 635 } 636 637 mConnectionState = ConnectionState.DISCONNECTING; 638 639 if (mConnectThread != null) { 640 mConnectThread.interrupt(); 641 } 642 643 if (mObexSession != null) { 644 mObexSession.stop(); 645 } 646 } 647 648 @Override finalize()649 public void finalize() { 650 disconnect(); 651 } 652 653 /** 654 * Gets current connection state 655 * 656 * @return current connection state 657 * @see ConnectionState 658 */ getState()659 public ConnectionState getState() { 660 return mConnectionState; 661 } 662 enableNotifications()663 private boolean enableNotifications() { 664 Log.v(TAG, "enableNotifications()"); 665 666 if (mMnsService == null) { 667 mMnsService = new BluetoothMnsService(); 668 } 669 670 mMnsService.registerCallback(mMas.getMasInstanceId(), mSessionHandler); 671 672 BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(true); 673 return mObexSession.makeRequest(request); 674 } 675 disableNotifications()676 private boolean disableNotifications() { 677 Log.v(TAG, "enableNotifications()"); 678 679 if (mMnsService != null) { 680 mMnsService.unregisterCallback(mMas.getMasInstanceId()); 681 } 682 683 mMnsService = null; 684 685 BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(false); 686 return mObexSession.makeRequest(request); 687 } 688 689 /** 690 * Sets state of notifications for MAS instance 691 * <p> 692 * Once notifications are enabled, callback handler will receive 693 * {@link #EVENT_EVENT_REPORT} when new notification is received 694 * <p> 695 * Upon completion callback handler will receive 696 * {@link #EVENT_SET_NOTIFICATION_REGISTRATION} 697 * 698 * @param status <code>true</code> if notifications shall be enabled, 699 * <code>false</code> otherwise 700 * @return <code>true</code> if request has been sent, <code>false</code> 701 * otherwise 702 */ setNotificationRegistration(boolean status)703 public boolean setNotificationRegistration(boolean status) { 704 if (mObexSession == null) { 705 return false; 706 } 707 708 if (status) { 709 return enableNotifications(); 710 } else { 711 return disableNotifications(); 712 } 713 } 714 715 /** 716 * Gets current state of notifications for MAS instance 717 * 718 * @return <code>true</code> if notifications are enabled, 719 * <code>false</code> otherwise 720 */ getNotificationRegistration()721 public boolean getNotificationRegistration() { 722 return mNotificationEnabled; 723 } 724 725 /** 726 * Goes back to root of folder hierarchy 727 * <p> 728 * Upon completion callback handler will receive {@link #EVENT_SET_PATH} 729 * 730 * @return <code>true</code> if request has been sent, <code>false</code> 731 * otherwise 732 */ setFolderRoot()733 public boolean setFolderRoot() { 734 if (mObexSession == null) { 735 return false; 736 } 737 738 BluetoothMasRequest request = new BluetoothMasRequestSetPath(true); 739 return mObexSession.makeRequest(request); 740 } 741 742 /** 743 * Goes back to parent folder in folder hierarchy 744 * <p> 745 * Upon completion callback handler will receive {@link #EVENT_SET_PATH} 746 * 747 * @return <code>true</code> if request has been sent, <code>false</code> 748 * otherwise 749 */ setFolderUp()750 public boolean setFolderUp() { 751 if (mObexSession == null) { 752 return false; 753 } 754 755 BluetoothMasRequest request = new BluetoothMasRequestSetPath(false); 756 return mObexSession.makeRequest(request); 757 } 758 759 /** 760 * Goes down to specified sub-folder in folder hierarchy 761 * <p> 762 * Upon completion callback handler will receive {@link #EVENT_SET_PATH} 763 * 764 * @param name name of sub-folder 765 * @return <code>true</code> if request has been sent, <code>false</code> 766 * otherwise 767 */ setFolderDown(String name)768 public boolean setFolderDown(String name) { 769 if (mObexSession == null) { 770 return false; 771 } 772 773 if (name == null || name.isEmpty() || name.contains("/")) { 774 return false; 775 } 776 777 BluetoothMasRequest request = new BluetoothMasRequestSetPath(name); 778 return mObexSession.makeRequest(request); 779 } 780 781 /** 782 * Gets current path in folder hierarchy 783 * 784 * @return current path 785 */ getCurrentPath()786 public String getCurrentPath() { 787 if (mPath.size() == 0) { 788 return ""; 789 } 790 791 Iterator<String> iter = mPath.iterator(); 792 793 StringBuilder sb = new StringBuilder(iter.next()); 794 795 while (iter.hasNext()) { 796 sb.append("/").append(iter.next()); 797 } 798 799 return sb.toString(); 800 } 801 802 /** 803 * Gets list of sub-folders in current folder 804 * <p> 805 * Upon completion callback handler will receive 806 * {@link #EVENT_GET_FOLDER_LISTING} 807 * 808 * @return <code>true</code> if request has been sent, <code>false</code> 809 * otherwise 810 */ getFolderListing()811 public boolean getFolderListing() { 812 return getFolderListing((short) 0, (short) 0); 813 } 814 815 /** 816 * Gets list of sub-folders in current folder 817 * <p> 818 * Upon completion callback handler will receive 819 * {@link #EVENT_GET_FOLDER_LISTING} 820 * 821 * @param maxListCount maximum number of items returned or <code>0</code> 822 * for default value 823 * @param listStartOffset index of first item returned or <code>0</code> for 824 * default value 825 * @return <code>true</code> if request has been sent, <code>false</code> 826 * otherwise 827 * @throws IllegalArgumentException if either maxListCount or 828 * listStartOffset are outside allowed range [0..65535] 829 */ getFolderListing(int maxListCount, int listStartOffset)830 public boolean getFolderListing(int maxListCount, int listStartOffset) { 831 if (mObexSession == null) { 832 return false; 833 } 834 835 BluetoothMasRequest request = new BluetoothMasRequestGetFolderListing(maxListCount, 836 listStartOffset); 837 return mObexSession.makeRequest(request); 838 } 839 840 /** 841 * Gets number of sub-folders in current folder 842 * <p> 843 * Upon completion callback handler will receive 844 * {@link #EVENT_GET_FOLDER_LISTING_SIZE} 845 * 846 * @return <code>true</code> if request has been sent, <code>false</code> 847 * otherwise 848 */ getFolderListingSize()849 public boolean getFolderListingSize() { 850 if (mObexSession == null) { 851 return false; 852 } 853 854 BluetoothMasRequest request = new BluetoothMasRequestGetFolderListingSize(); 855 return mObexSession.makeRequest(request); 856 } 857 858 /** 859 * Gets list of messages in specified sub-folder 860 * <p> 861 * Upon completion callback handler will receive 862 * {@link #EVENT_GET_MESSAGES_LISTING} 863 * 864 * @param folder name of sub-folder or <code>null</code> for current folder 865 * @param parameters bit-mask specifying requested parameters in listing or 866 * <code>0</code> for default value 867 * @return <code>true</code> if request has been sent, <code>false</code> 868 * otherwise 869 */ getMessagesListing(String folder, int parameters)870 public boolean getMessagesListing(String folder, int parameters) { 871 return getMessagesListing(folder, parameters, null, (byte) 0, 0, 0); 872 } 873 874 /** 875 * Gets list of messages in specified sub-folder 876 * <p> 877 * Upon completion callback handler will receive 878 * {@link #EVENT_GET_MESSAGES_LISTING} 879 * 880 * @param folder name of sub-folder or <code>null</code> for current folder 881 * @param parameters corresponds to <code>ParameterMask</code> application 882 * parameter in MAP specification 883 * @param filter {@link MessagesFilter} object describing filters to be 884 * applied on listing by MSE 885 * @param subjectLength maximum length of message subject in returned 886 * listing or <code>0</code> for default value 887 * @return <code>true</code> if request has been sent, <code>false</code> 888 * otherwise 889 * @throws IllegalArgumentException if subjectLength is outside allowed 890 * range [0..255] 891 */ getMessagesListing(String folder, int parameters, MessagesFilter filter, int subjectLength)892 public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter, 893 int subjectLength) { 894 895 return getMessagesListing(folder, parameters, filter, subjectLength, 0, 0); 896 } 897 898 /** 899 * Gets list of messages in specified sub-folder 900 * <p> 901 * Upon completion callback handler will receive 902 * {@link #EVENT_GET_MESSAGES_LISTING} 903 * 904 * @param folder name of sub-folder or <code>null</code> for current folder 905 * @param parameters corresponds to <code>ParameterMask</code> application 906 * parameter in MAP specification 907 * @param filter {@link MessagesFilter} object describing filters to be 908 * applied on listing by MSE 909 * @param subjectLength maximum length of message subject in returned 910 * listing or <code>0</code> for default value 911 * @param maxListCount maximum number of items returned or <code>0</code> 912 * for default value 913 * @param listStartOffset index of first item returned or <code>0</code> for 914 * default value 915 * @return <code>true</code> if request has been sent, <code>false</code> 916 * otherwise 917 * @throws IllegalArgumentException if subjectLength is outside allowed 918 * range [0..255] or either maxListCount or listStartOffset are 919 * outside allowed range [0..65535] 920 */ getMessagesListing(String folder, int parameters, MessagesFilter filter, int subjectLength, int maxListCount, int listStartOffset)921 public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter, 922 int subjectLength, int maxListCount, int listStartOffset) { 923 924 if (mObexSession == null) { 925 return false; 926 } 927 928 BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListing(folder, 929 parameters, filter, subjectLength, maxListCount, listStartOffset); 930 return mObexSession.makeRequest(request); 931 } 932 933 /** 934 * Gets number of messages in current folder 935 * <p> 936 * Upon completion callback handler will receive 937 * {@link #EVENT_GET_MESSAGES_LISTING_SIZE} 938 * 939 * @return <code>true</code> if request has been sent, <code>false</code> 940 * otherwise 941 */ getMessagesListingSize()942 public boolean getMessagesListingSize() { 943 if (mObexSession == null) { 944 return false; 945 } 946 947 BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListingSize(); 948 return mObexSession.makeRequest(request); 949 } 950 951 /** 952 * Retrieves message from MSE 953 * <p> 954 * Upon completion callback handler will receive {@link #EVENT_GET_MESSAGE} 955 * 956 * @param handle handle of message to retrieve 957 * @param charset {@link CharsetType} object corresponding to 958 * <code>Charset</code> application parameter in MAP 959 * specification 960 * @param attachment corresponds to <code>Attachment</code> application 961 * parameter in MAP specification 962 * @return <code>true</code> if request has been sent, <code>false</code> 963 * otherwise 964 */ getMessage(String handle, boolean attachment)965 public boolean getMessage(String handle, boolean attachment) { 966 if (mObexSession == null) { 967 return false; 968 } 969 970 try { 971 /* just to validate */ 972 new BigInteger(handle, 16); 973 } catch (NumberFormatException e) { 974 return false; 975 } 976 977 // Since we support only text messaging via Bluetooth, it is OK to restrict the requests to 978 // force conversion to UTF-8. 979 BluetoothMasRequest request = 980 new BluetoothMasRequestGetMessage(handle, CharsetType.UTF_8, attachment); 981 return mObexSession.makeRequest(request); 982 } 983 984 /** 985 * Sets read status of message on MSE 986 * <p> 987 * Upon completion callback handler will receive 988 * {@link #EVENT_SET_MESSAGE_STATUS} 989 * 990 * @param handle handle of message 991 * @param read <code>true</code> for "read", <code>false</code> for "unread" 992 * @return <code>true</code> if request has been sent, <code>false</code> 993 * otherwise 994 */ setMessageReadStatus(String handle, boolean read)995 public boolean setMessageReadStatus(String handle, boolean read) { 996 if (mObexSession == null) { 997 return false; 998 } 999 1000 try { 1001 /* just to validate */ 1002 new BigInteger(handle, 16); 1003 } catch (NumberFormatException e) { 1004 return false; 1005 } 1006 1007 BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle, 1008 StatusIndicator.READ, read); 1009 return mObexSession.makeRequest(request); 1010 } 1011 1012 /** 1013 * Sets deleted status of message on MSE 1014 * <p> 1015 * Upon completion callback handler will receive 1016 * {@link #EVENT_SET_MESSAGE_STATUS} 1017 * 1018 * @param handle handle of message 1019 * @param deleted <code>true</code> for "deleted", <code>false</code> for 1020 * "undeleted" 1021 * @return <code>true</code> if request has been sent, <code>false</code> 1022 * otherwise 1023 */ setMessageDeletedStatus(String handle, boolean deleted)1024 public boolean setMessageDeletedStatus(String handle, boolean deleted) { 1025 if (mObexSession == null) { 1026 return false; 1027 } 1028 1029 try { 1030 /* just to validate */ 1031 new BigInteger(handle, 16); 1032 } catch (NumberFormatException e) { 1033 return false; 1034 } 1035 1036 BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle, 1037 StatusIndicator.DELETED, deleted); 1038 return mObexSession.makeRequest(request); 1039 } 1040 1041 /** 1042 * Pushes new message to MSE 1043 * <p> 1044 * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE} 1045 * 1046 * @param folder name of sub-folder to push to or <code>null</code> for 1047 * current folder 1048 * @param charset {@link CharsetType} object corresponding to 1049 * <code>Charset</code> application parameter in MAP 1050 * specification 1051 * @return <code>true</code> if request has been sent, <code>false</code> 1052 * otherwise 1053 */ pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset)1054 public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset) { 1055 return pushMessage(folder, bmsg, charset, false, false); 1056 } 1057 1058 /** 1059 * Pushes new message to MSE 1060 * <p> 1061 * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE} 1062 * 1063 * @param folder name of sub-folder to push to or <code>null</code> for 1064 * current folder 1065 * @param bmsg {@link BluetoothMapBmessage} object representing message to 1066 * be pushed 1067 * @param charset {@link CharsetType} object corresponding to 1068 * <code>Charset</code> application parameter in MAP 1069 * specification 1070 * @param transparent corresponds to <code>Transparent</code> application 1071 * parameter in MAP specification 1072 * @param retry corresponds to <code>Transparent</code> application 1073 * parameter in MAP specification 1074 * @return <code>true</code> if request has been sent, <code>false</code> 1075 * otherwise 1076 */ pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset, boolean transparent, boolean retry)1077 public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset, 1078 boolean transparent, boolean retry) { 1079 if (mObexSession == null) { 1080 return false; 1081 } 1082 1083 String bmsgString = BluetoothMapBmessageBuilder.createBmessage(bmsg); 1084 1085 BluetoothMasRequest request = 1086 new BluetoothMasRequestPushMessage(folder, bmsgString, charset, transparent, retry); 1087 return mObexSession.makeRequest(request); 1088 } 1089 1090 /** 1091 * Requests MSE to initiate ubdate of inbox 1092 * <p> 1093 * Upon completion callback handler will receive {@link #EVENT_UPDATE_INBOX} 1094 * 1095 * @return <code>true</code> if request has been sent, <code>false</code> 1096 * otherwise 1097 */ updateInbox()1098 public boolean updateInbox() { 1099 if (mObexSession == null) { 1100 return false; 1101 } 1102 1103 BluetoothMasRequest request = new BluetoothMasRequestUpdateInbox(); 1104 return mObexSession.makeRequest(request); 1105 } 1106 } 1107