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