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