/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.bluetooth.client.map; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothMasInstance; import android.bluetooth.BluetoothSocket; import android.bluetooth.SdpMasRecord; import android.os.Handler; import android.os.Message; import android.util.Log; import android.bluetooth.client.map.BluetoothMasRequestSetMessageStatus.StatusIndicator; import android.bluetooth.client.map.utils.ObexTime; import java.io.IOException; import java.lang.ref.WeakReference; import java.math.BigInteger; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import javax.obex.ObexTransport; public class BluetoothMasClient { private final static String TAG = "BluetoothMasClient"; private static final int SOCKET_CONNECTED = 10; private static final int SOCKET_ERROR = 11; /** * Callback message sent when connection state changes *
* arg1
is set to {@link #STATUS_OK} when connection is
* established successfully and {@link #STATUS_FAILED} when connection
* either failed or was disconnected (depends on request from application)
*
* @see #connect()
* @see #disconnect()
*/
public static final int EVENT_CONNECT = 1;
/**
* Callback message sent when MSE accepted update inbox request
*
* @see #updateInbox()
*/
public static final int EVENT_UPDATE_INBOX = 2;
/**
* Callback message sent when path is changed
*
* obj
is set to path currently set on MSE
*
* @see #setFolderRoot()
* @see #setFolderUp()
* @see #setFolderDown(String)
*/
public static final int EVENT_SET_PATH = 3;
/**
* Callback message sent when folder listing is received
*
* obj
contains ArrayList of sub-folder names
*
* @see #getFolderListing()
* @see #getFolderListing(int, int)
*/
public static final int EVENT_GET_FOLDER_LISTING = 4;
/**
* Callback message sent when folder listing size is received
*
* obj
contains number of items in folder listing
*
* @see #getFolderListingSize()
*/
public static final int EVENT_GET_FOLDER_LISTING_SIZE = 5;
/**
* Callback message sent when messages listing is received
*
* obj
contains ArrayList of {@link BluetoothMapBmessage}
*
* @see #getMessagesListing(String, int)
* @see #getMessagesListing(String, int, MessagesFilter, int)
* @see #getMessagesListing(String, int, MessagesFilter, int, int, int)
*/
public static final int EVENT_GET_MESSAGES_LISTING = 6;
/**
* Callback message sent when message is received
*
* obj
contains {@link BluetoothMapBmessage}
*
* @see #getMessage(String, CharsetType, boolean)
*/
public static final int EVENT_GET_MESSAGE = 7;
/**
* Callback message sent when message status is changed
*
* @see #setMessageDeletedStatus(String, boolean)
* @see #setMessageReadStatus(String, boolean)
*/
public static final int EVENT_SET_MESSAGE_STATUS = 8;
/**
* Callback message sent when message is pushed to MSE
*
* obj
contains handle of message as allocated by MSE
*
* @see #pushMessage(String, BluetoothMapBmessage, CharsetType)
* @see #pushMessage(String, BluetoothMapBmessage, CharsetType, boolean,
* boolean)
*/
public static final int EVENT_PUSH_MESSAGE = 9;
/**
* Callback message sent when notification status is changed
*
* obj
contains 1
if notifications are enabled and
* 0
otherwise
*
* @see #setNotificationRegistration(boolean)
*/
public static final int EVENT_SET_NOTIFICATION_REGISTRATION = 10;
/**
* Callback message sent when event report is received from MSE to MNS
*
* obj
contains {@link BluetoothMapEventReport}
*
* @see #setNotificationRegistration(boolean)
*/
public static final int EVENT_EVENT_REPORT = 11;
/**
* Callback message sent when messages listing size is received
*
*
* Upon completion callback handler will receive {@link #EVENT_CONNECT}
*/
public void connect() {
if (mSessionHandler == null) {
mSessionHandler = new SessionHandler(this);
}
if (mConnectThread == null && mObexSession == null) {
mConnectionState = ConnectionState.CONNECTING;
mConnectThread = new SocketConnectThread();
mConnectThread.start();
}
}
/**
* Disconnects from MAS instance
*
* Upon completion callback handler will receive {@link #EVENT_CONNECT}
*/
public void disconnect() {
if (mConnectThread == null && mObexSession == null) {
return;
}
mConnectionState = ConnectionState.DISCONNECTING;
if (mConnectThread != null) {
mConnectThread.interrupt();
}
if (mObexSession != null) {
mObexSession.stop();
}
}
@Override
public void finalize() {
disconnect();
}
/**
* Gets current connection state
*
* @return current connection state
* @see ConnectionState
*/
public ConnectionState getState() {
return mConnectionState;
}
private boolean enableNotifications() {
Log.v(TAG, "enableNotifications()");
if (mMnsService == null) {
mMnsService = new BluetoothMnsService();
}
mMnsService.registerCallback(mMas.getMasInstanceId(), mSessionHandler);
BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(true);
return mObexSession.makeRequest(request);
}
private boolean disableNotifications() {
Log.v(TAG, "enableNotifications()");
if (mMnsService != null) {
mMnsService.unregisterCallback(mMas.getMasInstanceId());
}
mMnsService = null;
BluetoothMasRequest request = new BluetoothMasRequestSetNotificationRegistration(false);
return mObexSession.makeRequest(request);
}
/**
* Sets state of notifications for MAS instance
*
* Once notifications are enabled, callback handler will receive
* {@link #EVENT_EVENT_REPORT} when new notification is received
*
* Upon completion callback handler will receive
* {@link #EVENT_SET_NOTIFICATION_REGISTRATION}
*
* @param status
* Upon completion callback handler will receive {@link #EVENT_SET_PATH}
*
* @return
* Upon completion callback handler will receive {@link #EVENT_SET_PATH}
*
* @return
* Upon completion callback handler will receive {@link #EVENT_SET_PATH}
*
* @param name name of sub-folder
* @return
* Upon completion callback handler will receive
* {@link #EVENT_GET_FOLDER_LISTING}
*
* @return
* Upon completion callback handler will receive
* {@link #EVENT_GET_FOLDER_LISTING}
*
* @param maxListCount maximum number of items returned or
* Upon completion callback handler will receive
* {@link #EVENT_GET_FOLDER_LISTING_SIZE}
*
* @return
* Upon completion callback handler will receive
* {@link #EVENT_GET_MESSAGES_LISTING}
*
* @param folder name of sub-folder or
* Upon completion callback handler will receive
* {@link #EVENT_GET_MESSAGES_LISTING}
*
* @param folder name of sub-folder or
* Upon completion callback handler will receive
* {@link #EVENT_GET_MESSAGES_LISTING}
*
* @param folder name of sub-folder or
* Upon completion callback handler will receive
* {@link #EVENT_GET_MESSAGES_LISTING_SIZE}
*
* @return
* Upon completion callback handler will receive {@link #EVENT_GET_MESSAGE}
*
* @param handle handle of message to retrieve
* @param charset {@link CharsetType} object corresponding to
*
* Upon completion callback handler will receive
* {@link #EVENT_SET_MESSAGE_STATUS}
*
* @param handle handle of message
* @param read
* Upon completion callback handler will receive
* {@link #EVENT_SET_MESSAGE_STATUS}
*
* @param handle handle of message
* @param deleted
* Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
*
* @param folder name of sub-folder to push to or
* Upon completion callback handler will receive {@link #EVENT_PUSH_MESSAGE}
*
* @param folder name of sub-folder to push to or
* Upon completion callback handler will receive {@link #EVENT_UPDATE_INBOX}
*
* @return obj
contains number of items in messages listing
*
* @see #getMessagesListingSize()
*/
public static final int EVENT_GET_MESSAGES_LISTING_SIZE = 12;
/**
* Status for callback message when request is successful
*/
public static final int STATUS_OK = 0;
/**
* Status for callback message when request is not successful
*/
public static final int STATUS_FAILED = 1;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_DEFAULT = 0x00000000;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_SUBJECT = 0x00000001;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_DATETIME = 0x00000002;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_SENDER_NAME = 0x00000004;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_SENDER_ADDRESSING = 0x00000008;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_RECIPIENT_NAME = 0x00000010;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_RECIPIENT_ADDRESSING = 0x00000020;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_TYPE = 0x00000040;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_SIZE = 0x00000080;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_RECEPTION_STATUS = 0x00000100;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_TEXT = 0x00000200;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_ATTACHMENT_SIZE = 0x00000400;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_PRIORITY = 0x00000800;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_READ = 0x00001000;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_SENT = 0x00002000;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_PROTECTED = 0x00004000;
/**
* Constant corresponding to ParameterMask
application
* parameter value in MAP specification
*/
public static final int PARAMETER_REPLYTO_ADDRESSING = 0x00008000;
public enum ConnectionState {
DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING;
}
public enum CharsetType {
NATIVE, UTF_8;
}
/** device associated with client */
private final BluetoothDevice mDevice;
/** MAS instance associated with client */
private final SdpMasRecord mMas;
/** callback handler to application */
private final Handler mCallback;
private ConnectionState mConnectionState = ConnectionState.DISCONNECTED;
private boolean mNotificationEnabled = false;
private SocketConnectThread mConnectThread = null;
private ObexTransport mObexTransport = null;
private BluetoothMasObexClientSession mObexSession = null;
private SessionHandler mSessionHandler = null;
private BluetoothMnsService mMnsService = null;
private ArrayDequearg1
set to either
* {@link #STATUS_OK} or {@link #STATUS_FAILED} and
* arg2
to MAS instance ID. obj
in
* message is event specific.
*/
public BluetoothMasClient(BluetoothDevice device, SdpMasRecord mas,
Handler callback) {
mDevice = device;
mMas = mas;
mCallback = callback;
mPath = new ArrayDequetrue
if notifications shall be enabled,
* false
otherwise
* @return true
if request has been sent, false
* otherwise
*/
public boolean setNotificationRegistration(boolean status) {
if (mObexSession == null) {
return false;
}
if (status) {
return enableNotifications();
} else {
return disableNotifications();
}
}
/**
* Gets current state of notifications for MAS instance
*
* @return true
if notifications are enabled,
* false
otherwise
*/
public boolean getNotificationRegistration() {
return mNotificationEnabled;
}
/**
* Goes back to root of folder hierarchy
* true
if request has been sent, false
* otherwise
*/
public boolean setFolderRoot() {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestSetPath(true);
return mObexSession.makeRequest(request);
}
/**
* Goes back to parent folder in folder hierarchy
* true
if request has been sent, false
* otherwise
*/
public boolean setFolderUp() {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestSetPath(false);
return mObexSession.makeRequest(request);
}
/**
* Goes down to specified sub-folder in folder hierarchy
* true
if request has been sent, false
* otherwise
*/
public boolean setFolderDown(String name) {
if (mObexSession == null) {
return false;
}
if (name == null || name.isEmpty() || name.contains("/")) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestSetPath(name);
return mObexSession.makeRequest(request);
}
/**
* Gets current path in folder hierarchy
*
* @return current path
*/
public String getCurrentPath() {
if (mPath.size() == 0) {
return "";
}
Iteratortrue
if request has been sent, false
* otherwise
*/
public boolean getFolderListing() {
return getFolderListing((short) 0, (short) 0);
}
/**
* Gets list of sub-folders in current folder
* 0
* for default value
* @param listStartOffset index of first item returned or 0
for
* default value
* @return true
if request has been sent, false
* otherwise
* @throws IllegalArgumentException if either maxListCount or
* listStartOffset are outside allowed range [0..65535]
*/
public boolean getFolderListing(int maxListCount, int listStartOffset) {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestGetFolderListing(maxListCount,
listStartOffset);
return mObexSession.makeRequest(request);
}
/**
* Gets number of sub-folders in current folder
* true
if request has been sent, false
* otherwise
*/
public boolean getFolderListingSize() {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestGetFolderListingSize();
return mObexSession.makeRequest(request);
}
/**
* Gets list of messages in specified sub-folder
* null
for current folder
* @param parameters bit-mask specifying requested parameters in listing or
* 0
for default value
* @return true
if request has been sent, false
* otherwise
*/
public boolean getMessagesListing(String folder, int parameters) {
return getMessagesListing(folder, parameters, null, (byte) 0, 0, 0);
}
/**
* Gets list of messages in specified sub-folder
* null
for current folder
* @param parameters corresponds to ParameterMask
application
* parameter in MAP specification
* @param filter {@link MessagesFilter} object describing filters to be
* applied on listing by MSE
* @param subjectLength maximum length of message subject in returned
* listing or 0
for default value
* @return true
if request has been sent, false
* otherwise
* @throws IllegalArgumentException if subjectLength is outside allowed
* range [0..255]
*/
public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter,
int subjectLength) {
return getMessagesListing(folder, parameters, filter, subjectLength, 0, 0);
}
/**
* Gets list of messages in specified sub-folder
* null
for current folder
* @param parameters corresponds to ParameterMask
application
* parameter in MAP specification
* @param filter {@link MessagesFilter} object describing filters to be
* applied on listing by MSE
* @param subjectLength maximum length of message subject in returned
* listing or 0
for default value
* @param maxListCount maximum number of items returned or 0
* for default value
* @param listStartOffset index of first item returned or 0
for
* default value
* @return true
if request has been sent, false
* otherwise
* @throws IllegalArgumentException if subjectLength is outside allowed
* range [0..255] or either maxListCount or listStartOffset are
* outside allowed range [0..65535]
*/
public boolean getMessagesListing(String folder, int parameters, MessagesFilter filter,
int subjectLength, int maxListCount, int listStartOffset) {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListing(folder,
parameters, filter, subjectLength, maxListCount, listStartOffset);
return mObexSession.makeRequest(request);
}
/**
* Gets number of messages in current folder
* true
if request has been sent, false
* otherwise
*/
public boolean getMessagesListingSize() {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestGetMessagesListingSize();
return mObexSession.makeRequest(request);
}
/**
* Retrieves message from MSE
* Charset
application parameter in MAP
* specification
* @param attachment corresponds to Attachment
application
* parameter in MAP specification
* @return true
if request has been sent, false
* otherwise
*/
public boolean getMessage(String handle, boolean attachment) {
if (mObexSession == null) {
return false;
}
try {
/* just to validate */
new BigInteger(handle, 16);
} catch (NumberFormatException e) {
return false;
}
// Since we support only text messaging via Bluetooth, it is OK to restrict the requests to
// force conversion to UTF-8.
BluetoothMasRequest request =
new BluetoothMasRequestGetMessage(handle, CharsetType.UTF_8, attachment);
return mObexSession.makeRequest(request);
}
/**
* Sets read status of message on MSE
* true
for "read", false
for "unread"
* @return true
if request has been sent, false
* otherwise
*/
public boolean setMessageReadStatus(String handle, boolean read) {
if (mObexSession == null) {
return false;
}
try {
/* just to validate */
new BigInteger(handle, 16);
} catch (NumberFormatException e) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
StatusIndicator.READ, read);
return mObexSession.makeRequest(request);
}
/**
* Sets deleted status of message on MSE
* true
for "deleted", false
for
* "undeleted"
* @return true
if request has been sent, false
* otherwise
*/
public boolean setMessageDeletedStatus(String handle, boolean deleted) {
if (mObexSession == null) {
return false;
}
try {
/* just to validate */
new BigInteger(handle, 16);
} catch (NumberFormatException e) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestSetMessageStatus(handle,
StatusIndicator.DELETED, deleted);
return mObexSession.makeRequest(request);
}
/**
* Pushes new message to MSE
* null
for
* current folder
* @param charset {@link CharsetType} object corresponding to
* Charset
application parameter in MAP
* specification
* @return true
if request has been sent, false
* otherwise
*/
public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset) {
return pushMessage(folder, bmsg, charset, false, false);
}
/**
* Pushes new message to MSE
* null
for
* current folder
* @param bmsg {@link BluetoothMapBmessage} object representing message to
* be pushed
* @param charset {@link CharsetType} object corresponding to
* Charset
application parameter in MAP
* specification
* @param transparent corresponds to Transparent
application
* parameter in MAP specification
* @param retry corresponds to Transparent
application
* parameter in MAP specification
* @return true
if request has been sent, false
* otherwise
*/
public boolean pushMessage(String folder, BluetoothMapBmessage bmsg, CharsetType charset,
boolean transparent, boolean retry) {
if (mObexSession == null) {
return false;
}
String bmsgString = BluetoothMapBmessageBuilder.createBmessage(bmsg);
BluetoothMasRequest request =
new BluetoothMasRequestPushMessage(folder, bmsgString, charset, transparent, retry);
return mObexSession.makeRequest(request);
}
/**
* Requests MSE to initiate ubdate of inbox
* true
if request has been sent, false
* otherwise
*/
public boolean updateInbox() {
if (mObexSession == null) {
return false;
}
BluetoothMasRequest request = new BluetoothMasRequestUpdateInbox();
return mObexSession.makeRequest(request);
}
}