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.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 
    313         public SessionHandler(BluetoothMasClient client) {
    314             super();
    315 
    316             mClient = new WeakReference<BluetoothMasClient>(client);
    317         }
    318 
    319         @Override
    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 
    447     private void sendToClient(int event, boolean success) {
    448         sendToClient(event, success, null);
    449     }
    450 
    451     private void sendToClient(int event, boolean success, int param) {
    452         sendToClient(event, success, Integer.valueOf(param));
    453     }
    454 
    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 
    464         public SocketConnectThread() {
    465             super("SocketConnectThread");
    466         }
    467 
    468         @Override
    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
    487         public void interrupt() {
    488             closeSocket();
    489         }
    490 
    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 
    538         public MessagesFilter() {
    539         }
    540 
    541         public void setMessageType(byte filter) {
    542             messageType = filter;
    543         }
    544 
    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 
    553         public void setReadStatus(byte readfilter) {
    554             readStatus = readfilter;
    555         }
    556 
    557         public void setRecipient(String filter) {
    558             if ("".equals(filter)) {
    559                 recipient = null;
    560             } else {
    561                 recipient = filter;
    562             }
    563         }
    564 
    565         public void setOriginator(String filter) {
    566             if ("".equals(filter)) {
    567                 originator = null;
    568             } else {
    569                 originator = filter;
    570             }
    571         }
    572 
    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      */
    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      */
    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      */
    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      */
    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
    649     public void finalize() {
    650         disconnect();
    651     }
    652 
    653     /**
    654      * Gets current connection state
    655      *
    656      * @return current connection state
    657      * @see ConnectionState
    658      */
    659     public ConnectionState getState() {
    660         return mConnectionState;
    661     }
    662 
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
   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      */
   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      */
   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      */
   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