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
17package android.bluetooth.client.map;
18
19import android.bluetooth.BluetoothDevice;
20import android.bluetooth.BluetoothMasInstance;
21import android.bluetooth.BluetoothSocket;
22import android.bluetooth.SdpMasRecord;
23import android.os.Handler;
24import android.os.Message;
25import android.util.Log;
26
27import android.bluetooth.client.map.BluetoothMasRequestSetMessageStatus.StatusIndicator;
28import android.bluetooth.client.map.utils.ObexTime;
29
30import java.io.IOException;
31import java.lang.ref.WeakReference;
32import java.math.BigInteger;
33import java.util.ArrayDeque;
34import java.util.ArrayList;
35import java.util.Date;
36import java.util.Iterator;
37
38import javax.obex.ObexTransport;
39
40public 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, CharsetType charset, 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        BluetoothMasRequest request = new BluetoothMasRequestGetMessage(handle, charset,
978                attachment);
979        return mObexSession.makeRequest(request);
980    }
981
982    /**
983     * Sets read status of message on MSE
984     * <p>
985     * Upon completion callback handler will receive
986     * {@link #EVENT_SET_MESSAGE_STATUS}
987     *
988     * @param handle handle of message
989     * @param read <code>true</code> for "read", <code>false</code> for "unread"
990     * @return <code>true</code> if request has been sent, <code>false</code>
991     *         otherwise
992     */
993    public boolean setMessageReadStatus(String handle, boolean read) {
994        if (mObexSession == null) {
995            return false;
996        }
997
998        try {
999            /* just to validate */
1000            new BigInteger(handle, 16);
1001        } catch (NumberFormatException e) {
1002            return false;
1003        }
1004
1005        BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
1006                StatusIndicator.READ, read);
1007        return mObexSession.makeRequest(request);
1008    }
1009
1010    /**
1011     * Sets deleted status of message on MSE
1012     * <p>
1013     * Upon completion callback handler will receive
1014     * {@link #EVENT_SET_MESSAGE_STATUS}
1015     *
1016     * @param handle handle of message
1017     * @param deleted <code>true</code> for "deleted", <code>false</code> for
1018     *            "undeleted"
1019     * @return <code>true</code> if request has been sent, <code>false</code>
1020     *         otherwise
1021     */
1022    public boolean setMessageDeletedStatus(String handle, boolean deleted) {
1023        if (mObexSession == null) {
1024            return false;
1025        }
1026
1027        try {
1028            /* just to validate */
1029            new BigInteger(handle, 16);
1030        } catch (NumberFormatException e) {
1031            return false;
1032        }
1033
1034        BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
1035                StatusIndicator.DELETED, deleted);
1036        return mObexSession.makeRequest(request);
1037    }
1038
1039    /**
1040     * Pushes new message to MSE
1041     * <p>
1042     * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
1043     *
1044     * @param folder name of sub-folder to push to or <code>null</code> for
1045     *            current folder
1046     * @param charset {@link CharsetType} object corresponding to
1047     *            <code>Charset</code> application parameter in MAP
1048     *            specification
1049     * @return <code>true</code> if request has been sent, <code>false</code>
1050     *         otherwise
1051     */
1052    public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset) {
1053        return pushMessage(folder, bmsg, charset, false, false);
1054    }
1055
1056    /**
1057     * Pushes new message to MSE
1058     * <p>
1059     * Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
1060     *
1061     * @param folder name of sub-folder to push to or <code>null</code> for
1062     *            current folder
1063     * @param bmsg {@link BluetoothMapBmessage} object representing message to
1064     *            be pushed
1065     * @param charset {@link CharsetType} object corresponding to
1066     *            <code>Charset</code> application parameter in MAP
1067     *            specification
1068     * @param transparent corresponds to <code>Transparent</code> application
1069     *            parameter in MAP specification
1070     * @param retry corresponds to <code>Transparent</code> application
1071     *            parameter in MAP specification
1072     * @return <code>true</code> if request has been sent, <code>false</code>
1073     *         otherwise
1074     */
1075    public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset,
1076            boolean transparent, boolean retry) {
1077        if (mObexSession == null) {
1078            return false;
1079        }
1080
1081        String bmsgString = BluetoothMapBmessageBuilder.createBmessage(bmsg);
1082
1083        BluetoothMasRequest request =
1084                new BluetoothMasRequestPushMessage(folder, bmsgString, charset, transparent, retry);
1085        return mObexSession.makeRequest(request);
1086    }
1087
1088    /**
1089     * Requests MSE to initiate ubdate of inbox
1090     * <p>
1091     * Upon completion callback handler will receive {@link #EVENT_UPDATE_INBOX}
1092     *
1093     * @return <code>true</code> if request has been sent, <code>false</code>
1094     *         otherwise
1095     */
1096    public boolean updateInbox() {
1097        if (mObexSession == null) {
1098            return false;
1099        }
1100
1101        BluetoothMasRequest request = new BluetoothMasRequestUpdateInbox();
1102        return mObexSession.makeRequest(request);
1103    }
1104}
1105