OBEX Over L2CAP + SDP search API for BT profiles
- Updated OBEX to support SRM - Added support for OBEX over l2cap and SRM. - Minor bugfixes, and reduce CPU load ALOT - Added support to send responses without body data. - Extend BluetoothSocket to support L2CAP - Added functionality to get the channel number needed to be able to create an SDP record with the channel number. - Added interface to get socket type and max packet sizes. - Added interface to perform SDP search and get the resulting SDP record data. Change-Id: I9d37a00ce73dfffc0e3ce03eab5511ba3a86e5b8
This commit is contained in:
committed by
Andre Eisenbach
parent
31a94f48bf
commit
238e0f934f
@ -6505,10 +6505,16 @@ package android.bluetooth {
|
|||||||
public final class BluetoothSocket implements java.io.Closeable {
|
public final class BluetoothSocket implements java.io.Closeable {
|
||||||
method public void close() throws java.io.IOException;
|
method public void close() throws java.io.IOException;
|
||||||
method public void connect() throws java.io.IOException;
|
method public void connect() throws java.io.IOException;
|
||||||
|
method public int getConnectionType();
|
||||||
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
||||||
|
method public int getMaxReceivePacketSize();
|
||||||
|
method public int getMaxTransmitPacketSize();
|
||||||
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
||||||
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
||||||
method public boolean isConnected();
|
method public boolean isConnected();
|
||||||
|
field public static final int TYPE_L2CAP = 3; // 0x3
|
||||||
|
field public static final int TYPE_RFCOMM = 1; // 0x1
|
||||||
|
field public static final int TYPE_SCO = 2; // 0x2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6721,10 +6721,16 @@ package android.bluetooth {
|
|||||||
public final class BluetoothSocket implements java.io.Closeable {
|
public final class BluetoothSocket implements java.io.Closeable {
|
||||||
method public void close() throws java.io.IOException;
|
method public void close() throws java.io.IOException;
|
||||||
method public void connect() throws java.io.IOException;
|
method public void connect() throws java.io.IOException;
|
||||||
|
method public int getConnectionType();
|
||||||
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
method public java.io.InputStream getInputStream() throws java.io.IOException;
|
||||||
|
method public int getMaxReceivePacketSize();
|
||||||
|
method public int getMaxTransmitPacketSize();
|
||||||
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
method public java.io.OutputStream getOutputStream() throws java.io.IOException;
|
||||||
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
method public android.bluetooth.BluetoothDevice getRemoteDevice();
|
||||||
method public boolean isConnected();
|
method public boolean isConnected();
|
||||||
|
field public static final int TYPE_L2CAP = 3; // 0x3
|
||||||
|
field public static final int TYPE_RFCOMM = 1; // 0x1
|
||||||
|
field public static final int TYPE_SCO = 2; // 0x2
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2009-2014 The Android Open Source Project
|
* Copyright (C) 2009-2015 The Android Open Source Project
|
||||||
|
* Copyright (C) 2015 Samsung LSI
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -377,6 +378,18 @@ public final class BluetoothAdapter {
|
|||||||
/** @hide */
|
/** @hide */
|
||||||
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
|
public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
|
||||||
|
|
||||||
|
|
||||||
|
/** When creating a ServerSocket using listenUsingRfcommOn() or
|
||||||
|
* listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create
|
||||||
|
* a ServerSocket that auto assigns a channel number to the first
|
||||||
|
* bluetooth socket.
|
||||||
|
* The channel number assigned to this first Bluetooth Socket will
|
||||||
|
* be stored in the ServerSocket, and reused for subsequent Bluetooth
|
||||||
|
* sockets.
|
||||||
|
* @hide */
|
||||||
|
public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2;
|
||||||
|
|
||||||
|
|
||||||
private static final int ADDRESS_LENGTH = 17;
|
private static final int ADDRESS_LENGTH = 17;
|
||||||
|
|
||||||
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
|
private static final int CONTROLLER_ENERGY_UPDATE_TIMEOUT_MILLIS = 30;
|
||||||
@ -1144,6 +1157,9 @@ public final class BluetoothAdapter {
|
|||||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||||
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
|
BluetoothSocket.TYPE_RFCOMM, true, true, channel);
|
||||||
int errno = socket.mSocket.bindListen();
|
int errno = socket.mSocket.bindListen();
|
||||||
|
if(channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
|
socket.setChannel(socket.mSocket.getPort());
|
||||||
|
}
|
||||||
if (errno != 0) {
|
if (errno != 0) {
|
||||||
//TODO(BT): Throw the same exception error code
|
//TODO(BT): Throw the same exception error code
|
||||||
// that the previous code was using.
|
// that the previous code was using.
|
||||||
@ -1278,6 +1294,9 @@ public final class BluetoothAdapter {
|
|||||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||||
BluetoothSocket.TYPE_RFCOMM, false, false, port);
|
BluetoothSocket.TYPE_RFCOMM, false, false, port);
|
||||||
int errno = socket.mSocket.bindListen();
|
int errno = socket.mSocket.bindListen();
|
||||||
|
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
|
socket.setChannel(socket.mSocket.getPort());
|
||||||
|
}
|
||||||
if (errno != 0) {
|
if (errno != 0) {
|
||||||
//TODO(BT): Throw the same exception error code
|
//TODO(BT): Throw the same exception error code
|
||||||
// that the previous code was using.
|
// that the previous code was using.
|
||||||
@ -1300,6 +1319,9 @@ public final class BluetoothAdapter {
|
|||||||
BluetoothServerSocket socket = new BluetoothServerSocket(
|
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||||
BluetoothSocket.TYPE_RFCOMM, false, true, port);
|
BluetoothSocket.TYPE_RFCOMM, false, true, port);
|
||||||
int errno = socket.mSocket.bindListen();
|
int errno = socket.mSocket.bindListen();
|
||||||
|
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
|
socket.setChannel(socket.mSocket.getPort());
|
||||||
|
}
|
||||||
if (errno < 0) {
|
if (errno < 0) {
|
||||||
//TODO(BT): Throw the same exception error code
|
//TODO(BT): Throw the same exception error code
|
||||||
// that the previous code was using.
|
// that the previous code was using.
|
||||||
@ -1329,6 +1351,30 @@ public final class BluetoothAdapter {
|
|||||||
return socket;
|
return socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct an encrypted, authenticated, L2CAP server socket.
|
||||||
|
* Call #accept to retrieve connections to this socket.
|
||||||
|
* @return An L2CAP BluetoothServerSocket
|
||||||
|
* @throws IOException On error, for example Bluetooth not available, or
|
||||||
|
* insufficient permissions.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
|
||||||
|
BluetoothServerSocket socket = new BluetoothServerSocket(
|
||||||
|
BluetoothSocket.TYPE_L2CAP, true, true, port);
|
||||||
|
int errno = socket.mSocket.bindListen();
|
||||||
|
if(port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
|
socket.setChannel(socket.mSocket.getPort());
|
||||||
|
}
|
||||||
|
if (errno != 0) {
|
||||||
|
//TODO(BT): Throw the same exception error code
|
||||||
|
// that the previous code was using.
|
||||||
|
//socket.mSocket.throwErrnoNative(errno);
|
||||||
|
throw new IOException("Error: " + errno);
|
||||||
|
}
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read the local Out of Band Pairing Data
|
* Read the local Out of Band Pairing Data
|
||||||
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
||||||
|
@ -302,6 +302,12 @@ public final class BluetoothDevice implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final int DEVICE_TYPE_DUAL = 3;
|
public static final int DEVICE_TYPE_DUAL = 3;
|
||||||
|
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||||
|
public static final String ACTION_SDP_RECORD =
|
||||||
|
"android.bluetooth.device.action.SDP_RECORD";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast Action: This intent is used to broadcast the {@link UUID}
|
* Broadcast Action: This intent is used to broadcast the {@link UUID}
|
||||||
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
|
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
|
||||||
@ -526,6 +532,13 @@ public final class BluetoothDevice implements Parcelable {
|
|||||||
*/
|
*/
|
||||||
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
|
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final String EXTRA_SDP_RECORD =
|
||||||
|
"android.bluetooth.device.extra.SDP_RECORD";
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final String EXTRA_SDP_SEARCH_STATUS =
|
||||||
|
"android.bluetooth.device.extra.SDP_SEARCH_STATUS";
|
||||||
/**
|
/**
|
||||||
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
|
* For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
|
||||||
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
|
* {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
|
||||||
@ -1054,14 +1067,34 @@ public final class BluetoothDevice implements Parcelable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a service discovery on the remote device to get the SDP records associated
|
||||||
|
* with the specified UUID.
|
||||||
|
*
|
||||||
|
* <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
|
||||||
|
* with the SDP records found on the remote end. If there is an error
|
||||||
|
* in getting the SDP records or if the process takes a long time,
|
||||||
|
* {@link #ACTION_SDP_RECORD} intent is sent with an status value in
|
||||||
|
* {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
|
||||||
|
* Detailed status error codes can be found by members of the Bluetooth package in
|
||||||
|
* the AbstractionLayer class.
|
||||||
|
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
|
||||||
|
* The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
|
||||||
|
* The object type will match one of the SdpXxxRecord types, depending on the UUID searched
|
||||||
|
* for.
|
||||||
|
*
|
||||||
|
* @return False if the sanity check fails, True if the process
|
||||||
|
* of initiating an ACL connection to the remote device
|
||||||
|
* was started.
|
||||||
|
*/
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public boolean fetchMasInstances() {
|
public boolean sdpSearch(ParcelUuid uuid) {
|
||||||
if (sService == null) {
|
if (sService == null) {
|
||||||
Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances");
|
Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return sService.fetchRemoteMasInstances(this);
|
return sService.sdpSearch(this,uuid);
|
||||||
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1260,6 +1293,36 @@ public final class BluetoothDevice implements Parcelable {
|
|||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an L2cap {@link BluetoothSocket} ready to start a secure
|
||||||
|
* outgoing connection to this remote device on given channel.
|
||||||
|
* <p>The remote device will be authenticated and communication on this
|
||||||
|
* socket will be encrypted.
|
||||||
|
* <p> Use this socket only if an authenticated socket link is possible.
|
||||||
|
* Authentication refers to the authentication of the link key to
|
||||||
|
* prevent man-in-the-middle type of attacks.
|
||||||
|
* For example, for Bluetooth 2.1 devices, if any of the devices does not
|
||||||
|
* have an input and output capability or just has the ability to
|
||||||
|
* display a numeric key, a secure socket connection is not possible.
|
||||||
|
* In such a case, use {#link createInsecureRfcommSocket}.
|
||||||
|
* For more details, refer to the Security Model section 5.2 (vol 3) of
|
||||||
|
* Bluetooth Core Specification version 2.1 + EDR.
|
||||||
|
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
|
||||||
|
* connection.
|
||||||
|
* <p>Valid L2CAP PSM channels are in range 1 to 2^16.
|
||||||
|
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}
|
||||||
|
*
|
||||||
|
* @param channel L2cap PSM/channel to connect to
|
||||||
|
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
|
||||||
|
* @throws IOException on error, for example Bluetooth not available, or
|
||||||
|
* insufficient permissions
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public BluetoothSocket createL2capSocket(int channel) throws IOException {
|
||||||
|
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
|
* Create an RFCOMM {@link BluetoothSocket} ready to start a secure
|
||||||
* outgoing connection to this remote device using SDP lookup of uuid.
|
* outgoing connection to this remote device using SDP lookup of uuid.
|
||||||
|
@ -18,6 +18,7 @@ package android.bluetooth;
|
|||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.ParcelUuid;
|
import android.os.ParcelUuid;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -66,10 +67,11 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public final class BluetoothServerSocket implements Closeable {
|
public final class BluetoothServerSocket implements Closeable {
|
||||||
|
|
||||||
|
private static final String TAG = "BluetoothServerSocket";
|
||||||
/*package*/ final BluetoothSocket mSocket;
|
/*package*/ final BluetoothSocket mSocket;
|
||||||
private Handler mHandler;
|
private Handler mHandler;
|
||||||
private int mMessage;
|
private int mMessage;
|
||||||
private final int mChannel;
|
private int mChannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a socket for incoming connections.
|
* Construct a socket for incoming connections.
|
||||||
@ -84,6 +86,9 @@ public final class BluetoothServerSocket implements Closeable {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
mChannel = port;
|
mChannel = port;
|
||||||
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
|
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
|
||||||
|
if(port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
|
mSocket.setExcludeSdp(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,6 +103,7 @@ public final class BluetoothServerSocket implements Closeable {
|
|||||||
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
|
/*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
|
mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
|
||||||
|
// TODO: This is the same as mChannel = -1 - is this intentional?
|
||||||
mChannel = mSocket.getPort();
|
mChannel = mSocket.getPort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +159,7 @@ public final class BluetoothServerSocket implements Closeable {
|
|||||||
/*package*/ void setServiceName(String ServiceName) {
|
/*package*/ void setServiceName(String ServiceName) {
|
||||||
mSocket.setServiceName(ServiceName);
|
mSocket.setServiceName(ServiceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the channel on which this socket is bound.
|
* Returns the channel on which this socket is bound.
|
||||||
* @hide
|
* @hide
|
||||||
@ -160,4 +167,47 @@ public final class BluetoothServerSocket implements Closeable {
|
|||||||
public int getChannel() {
|
public int getChannel() {
|
||||||
return mChannel;
|
return mChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the channel on which future sockets are bound.
|
||||||
|
* Currently used only when a channel is auto generated.
|
||||||
|
*/
|
||||||
|
/*package*/ void setChannel(int newChannel) {
|
||||||
|
/* TODO: From a design/architecture perspective this is wrong.
|
||||||
|
* The bind operation should be conducted through this class
|
||||||
|
* and the resulting port should be kept in mChannel, and
|
||||||
|
* not set from BluetoothAdapter. */
|
||||||
|
if(mSocket != null) {
|
||||||
|
if(mSocket.getPort() != newChannel) {
|
||||||
|
Log.w(TAG,"The port set is different that the underlying port. mSocket.getPort(): "
|
||||||
|
+ mSocket.getPort() + " requested newChannel: " + newChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mChannel = newChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("ServerSocket: Type: ");
|
||||||
|
switch(mSocket.getConnectionType()) {
|
||||||
|
case BluetoothSocket.TYPE_RFCOMM:
|
||||||
|
{
|
||||||
|
sb.append("TYPE_RFCOMM");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BluetoothSocket.TYPE_L2CAP:
|
||||||
|
{
|
||||||
|
sb.append("TYPE_L2CAP");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BluetoothSocket.TYPE_SCO:
|
||||||
|
{
|
||||||
|
sb.append("TYPE_SCO");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(" Channel: ").append(mChannel);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import android.os.ParcelFileDescriptor;
|
|||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -29,6 +30,8 @@ import java.io.OutputStream;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import android.net.LocalSocket;
|
import android.net.LocalSocket;
|
||||||
|
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
/**
|
/**
|
||||||
@ -86,17 +89,19 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final int MAX_RFCOMM_CHANNEL = 30;
|
public static final int MAX_RFCOMM_CHANNEL = 30;
|
||||||
|
/*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF;
|
||||||
|
|
||||||
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
|
/** Keep TYPE_ fields in sync with BluetoothSocket.cpp */
|
||||||
/*package*/ static final int TYPE_RFCOMM = 1;
|
public static final int TYPE_RFCOMM = 1;
|
||||||
/*package*/ static final int TYPE_SCO = 2;
|
public static final int TYPE_SCO = 2;
|
||||||
/*package*/ static final int TYPE_L2CAP = 3;
|
public static final int TYPE_L2CAP = 3;
|
||||||
|
|
||||||
/*package*/ static final int EBADFD = 77;
|
/*package*/ static final int EBADFD = 77;
|
||||||
/*package*/ static final int EADDRINUSE = 98;
|
/*package*/ static final int EADDRINUSE = 98;
|
||||||
|
|
||||||
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
|
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
|
||||||
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
|
/*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
|
||||||
|
/*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
|
||||||
|
|
||||||
private final int mType; /* one of TYPE_RFCOMM etc */
|
private final int mType; /* one of TYPE_RFCOMM etc */
|
||||||
private BluetoothDevice mDevice; /* remote device */
|
private BluetoothDevice mDevice; /* remote device */
|
||||||
@ -106,6 +111,7 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
private final BluetoothInputStream mInputStream;
|
private final BluetoothInputStream mInputStream;
|
||||||
private final BluetoothOutputStream mOutputStream;
|
private final BluetoothOutputStream mOutputStream;
|
||||||
private final ParcelUuid mUuid;
|
private final ParcelUuid mUuid;
|
||||||
|
private boolean mExcludeSdp = false;
|
||||||
private ParcelFileDescriptor mPfd;
|
private ParcelFileDescriptor mPfd;
|
||||||
private LocalSocket mSocket;
|
private LocalSocket mSocket;
|
||||||
private InputStream mSocketIS;
|
private InputStream mSocketIS;
|
||||||
@ -115,7 +121,11 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
private String mServiceName;
|
private String mServiceName;
|
||||||
private static int PROXY_CONNECTION_TIMEOUT = 5000;
|
private static int PROXY_CONNECTION_TIMEOUT = 5000;
|
||||||
|
|
||||||
private static int SOCK_SIGNAL_SIZE = 16;
|
private static int SOCK_SIGNAL_SIZE = 20;
|
||||||
|
|
||||||
|
private ByteBuffer mL2capBuffer = null;
|
||||||
|
private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer.
|
||||||
|
private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received.
|
||||||
|
|
||||||
private enum SocketState {
|
private enum SocketState {
|
||||||
INIT,
|
INIT,
|
||||||
@ -144,12 +154,14 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
*/
|
*/
|
||||||
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
|
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
|
||||||
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
|
BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
|
||||||
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) {
|
if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
|
||||||
|
if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
|
||||||
|
&& port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
|
||||||
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
|
if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
|
||||||
throw new IOException("Invalid RFCOMM channel: " + port);
|
throw new IOException("Invalid RFCOMM channel: " + port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(uuid != null)
|
if (uuid != null)
|
||||||
mUuid = uuid;
|
mUuid = uuid;
|
||||||
else mUuid = new ParcelUuid(new UUID(0, 0));
|
else mUuid = new ParcelUuid(new UUID(0, 0));
|
||||||
mType = type;
|
mType = type;
|
||||||
@ -172,6 +184,7 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
mOutputStream = new BluetoothOutputStream(this);
|
mOutputStream = new BluetoothOutputStream(this);
|
||||||
}
|
}
|
||||||
private BluetoothSocket(BluetoothSocket s) {
|
private BluetoothSocket(BluetoothSocket s) {
|
||||||
|
if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
|
||||||
mUuid = s.mUuid;
|
mUuid = s.mUuid;
|
||||||
mType = s.mType;
|
mType = s.mType;
|
||||||
mAuth = s.mAuth;
|
mAuth = s.mAuth;
|
||||||
@ -179,7 +192,11 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
mPort = s.mPort;
|
mPort = s.mPort;
|
||||||
mInputStream = new BluetoothInputStream(this);
|
mInputStream = new BluetoothInputStream(this);
|
||||||
mOutputStream = new BluetoothOutputStream(this);
|
mOutputStream = new BluetoothOutputStream(this);
|
||||||
|
mMaxRxPacketSize = s.mMaxRxPacketSize;
|
||||||
|
mMaxTxPacketSize = s.mMaxTxPacketSize;
|
||||||
|
|
||||||
mServiceName = s.mServiceName;
|
mServiceName = s.mServiceName;
|
||||||
|
mExcludeSdp = s.mExcludeSdp;
|
||||||
}
|
}
|
||||||
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
|
private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException {
|
||||||
BluetoothSocket as = new BluetoothSocket(this);
|
BluetoothSocket as = new BluetoothSocket(this);
|
||||||
@ -229,6 +246,8 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
flags |= SEC_FLAG_AUTH;
|
flags |= SEC_FLAG_AUTH;
|
||||||
if(mEncrypt)
|
if(mEncrypt)
|
||||||
flags |= SEC_FLAG_ENCRYPT;
|
flags |= SEC_FLAG_ENCRYPT;
|
||||||
|
if(mExcludeSdp)
|
||||||
|
flags |= BTSOCK_FLAG_NO_SDP;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,7 +317,8 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
|
if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
|
||||||
IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
|
IBluetooth bluetoothProxy =
|
||||||
|
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
|
||||||
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
|
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
|
||||||
mPfd = bluetoothProxy.connectSocket(mDevice, mType,
|
mPfd = bluetoothProxy.connectSocket(mDevice, mType,
|
||||||
mUuid, mPort, getSecurityFlags());
|
mUuid, mPort, getSecurityFlags());
|
||||||
@ -370,7 +390,7 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
mSocketState = SocketState.LISTENING;
|
mSocketState = SocketState.LISTENING;
|
||||||
}
|
}
|
||||||
if (DBG) Log.d(TAG, "channel: " + channel);
|
if (DBG) Log.d(TAG, "channel: " + channel);
|
||||||
if (mPort == -1) {
|
if (mPort <= -1) {
|
||||||
mPort = channel;
|
mPort = channel;
|
||||||
} // else ASSERT(mPort == channel)
|
} // else ASSERT(mPort == channel)
|
||||||
ret = 0;
|
ret = 0;
|
||||||
@ -391,7 +411,8 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
|
|
||||||
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
|
/*package*/ BluetoothSocket accept(int timeout) throws IOException {
|
||||||
BluetoothSocket acceptedSocket;
|
BluetoothSocket acceptedSocket;
|
||||||
if (mSocketState != SocketState.LISTENING) throw new IOException("bt socket is not in listen state");
|
if (mSocketState != SocketState.LISTENING)
|
||||||
|
throw new IOException("bt socket is not in listen state");
|
||||||
if(timeout > 0) {
|
if(timeout > 0) {
|
||||||
Log.d(TAG, "accept() set timeout (ms):" + timeout);
|
Log.d(TAG, "accept() set timeout (ms):" + timeout);
|
||||||
mSocket.setSoTimeout(timeout);
|
mSocket.setSoTimeout(timeout);
|
||||||
@ -427,19 +448,71 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
|
/*package*/ int read(byte[] b, int offset, int length) throws IOException {
|
||||||
if (mSocketIS == null) throw new IOException("read is called on null InputStream");
|
int ret = 0;
|
||||||
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
|
if (VDBG) Log.d(TAG, "read in: " + mSocketIS + " len: " + length);
|
||||||
int ret = mSocketIS.read(b, offset, length);
|
if(mType == TYPE_L2CAP)
|
||||||
if(ret < 0)
|
{
|
||||||
|
int bytesToRead = length;
|
||||||
|
if (VDBG) Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
|
||||||
|
+ "mL2capBuffer= " + mL2capBuffer);
|
||||||
|
if (mL2capBuffer == null) {
|
||||||
|
createL2capRxBuffer();
|
||||||
|
}
|
||||||
|
if (mL2capBuffer.remaining() == 0) {
|
||||||
|
if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling...");
|
||||||
|
if (fillL2capRxBuffer() == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bytesToRead > mL2capBuffer.remaining()) {
|
||||||
|
bytesToRead = mL2capBuffer.remaining();
|
||||||
|
}
|
||||||
|
if(VDBG) Log.v(TAG, "get(): offset: " + offset
|
||||||
|
+ " bytesToRead: " + bytesToRead);
|
||||||
|
mL2capBuffer.get(b, offset, bytesToRead);
|
||||||
|
ret = bytesToRead;
|
||||||
|
}else {
|
||||||
|
if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length);
|
||||||
|
ret = mSocketIS.read(b, offset, length);
|
||||||
|
}
|
||||||
|
if (ret < 0)
|
||||||
throw new IOException("bt socket closed, read return: " + ret);
|
throw new IOException("bt socket closed, read return: " + ret);
|
||||||
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
|
if (VDBG) Log.d(TAG, "read out: " + mSocketIS + " ret: " + ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
|
/*package*/ int write(byte[] b, int offset, int length) throws IOException {
|
||||||
if (mSocketOS == null) throw new IOException("write is called on null OutputStream");
|
|
||||||
|
//TODO: Since bindings can exist between the SDU size and the
|
||||||
|
// protocol, we might need to throw an exception instead of just
|
||||||
|
// splitting the write into multiple smaller writes.
|
||||||
|
// Rfcomm uses dynamic allocation, and should not have any bindings
|
||||||
|
// to the actual message length.
|
||||||
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
|
if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
|
||||||
|
if (mType == TYPE_L2CAP) {
|
||||||
|
if(length <= mMaxTxPacketSize) {
|
||||||
mSocketOS.write(b, offset, length);
|
mSocketOS.write(b, offset, length);
|
||||||
|
} else {
|
||||||
|
int tmpOffset = offset;
|
||||||
|
int tmpLength = mMaxTxPacketSize;
|
||||||
|
int endIndex = offset + length;
|
||||||
|
boolean done = false;
|
||||||
|
if(DBG) Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n"
|
||||||
|
+ "Packet will be divided into SDU packets of size "
|
||||||
|
+ mMaxTxPacketSize);
|
||||||
|
do{
|
||||||
|
mSocketOS.write(b, tmpOffset, tmpLength);
|
||||||
|
tmpOffset += mMaxTxPacketSize;
|
||||||
|
if((tmpOffset + mMaxTxPacketSize) > endIndex) {
|
||||||
|
tmpLength = endIndex - tmpOffset;
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
} while(!done);
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mSocketOS.write(b, offset, length);
|
||||||
|
}
|
||||||
// There is no good way to confirm since the entire process is asynchronous anyway
|
// There is no good way to confirm since the entire process is asynchronous anyway
|
||||||
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
|
if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
|
||||||
return length;
|
return length;
|
||||||
@ -447,7 +520,8 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: " + mSocketState);
|
if (DBG) Log.d(TAG, "close() in, this: " + this + ", channel: " + mPort + ", state: "
|
||||||
|
+ mSocketState);
|
||||||
if(mSocketState == SocketState.CLOSED)
|
if(mSocketState == SocketState.CLOSED)
|
||||||
return;
|
return;
|
||||||
else
|
else
|
||||||
@ -457,8 +531,9 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
if(mSocketState == SocketState.CLOSED)
|
if(mSocketState == SocketState.CLOSED)
|
||||||
return;
|
return;
|
||||||
mSocketState = SocketState.CLOSED;
|
mSocketState = SocketState.CLOSED;
|
||||||
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS +
|
if (DBG) Log.d(TAG, "close() this: " + this + ", channel: " + mPort +
|
||||||
", mSocketOS: " + mSocketOS + "mSocket: " + mSocket);
|
", mSocketIS: " + mSocketIS + ", mSocketOS: " + mSocketOS +
|
||||||
|
"mSocket: " + mSocket);
|
||||||
if(mSocket != null) {
|
if(mSocket != null) {
|
||||||
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
|
if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
|
||||||
mSocket.shutdownInput();
|
mSocket.shutdownInput();
|
||||||
@ -480,6 +555,47 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
/*package */ int getPort() {
|
/*package */ int getPort() {
|
||||||
return mPort;
|
return mPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum supported Transmit packet size for the underlying transport.
|
||||||
|
* Use this to optimize the writes done to the output socket, to avoid sending
|
||||||
|
* half full packets.
|
||||||
|
* @return the maximum supported Transmit packet size for the underlying transport.
|
||||||
|
*/
|
||||||
|
public int getMaxTransmitPacketSize(){
|
||||||
|
return mMaxTxPacketSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum supported Receive packet size for the underlying transport.
|
||||||
|
* Use this to optimize the reads done on the input stream, as any call to read
|
||||||
|
* will return a maximum of this amount of bytes - or for some transports a
|
||||||
|
* multiple of this value.
|
||||||
|
* @return the maximum supported Receive packet size for the underlying transport.
|
||||||
|
*/
|
||||||
|
public int getMaxReceivePacketSize(){
|
||||||
|
return mMaxRxPacketSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of the underlying connection
|
||||||
|
* @return one of TYPE_
|
||||||
|
*/
|
||||||
|
public int getConnectionType() {
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change if a SDP entry should be automatically created.
|
||||||
|
* Must be called before calling .bind, for the call to have any effect.
|
||||||
|
* @param mExcludeSdp <li>TRUE - do not auto generate SDP record.
|
||||||
|
* <li>FALSE - default - auto generate SPP SDP record.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public void setExcludeSdp(boolean excludeSdp) {
|
||||||
|
this.mExcludeSdp = excludeSdp;
|
||||||
|
}
|
||||||
|
|
||||||
private String convertAddr(final byte[] addr) {
|
private String convertAddr(final byte[] addr) {
|
||||||
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
|
return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
|
addr[0] , addr[1], addr[2], addr[3] , addr[4], addr[5]);
|
||||||
@ -487,8 +603,10 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
private String waitSocketSignal(InputStream is) throws IOException {
|
private String waitSocketSignal(InputStream is) throws IOException {
|
||||||
byte [] sig = new byte[SOCK_SIGNAL_SIZE];
|
byte [] sig = new byte[SOCK_SIGNAL_SIZE];
|
||||||
int ret = readAll(is, sig);
|
int ret = readAll(is, sig);
|
||||||
if (VDBG) Log.d(TAG, "waitSocketSignal read 16 bytes signal ret: " + ret);
|
if (VDBG) Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE +
|
||||||
|
" bytes signal ret: " + ret);
|
||||||
ByteBuffer bb = ByteBuffer.wrap(sig);
|
ByteBuffer bb = ByteBuffer.wrap(sig);
|
||||||
|
/* the struct in native is decorated with __attribute__((packed)), hence this is possible */
|
||||||
bb.order(ByteOrder.nativeOrder());
|
bb.order(ByteOrder.nativeOrder());
|
||||||
int size = bb.getShort();
|
int size = bb.getShort();
|
||||||
if(size != SOCK_SIGNAL_SIZE)
|
if(size != SOCK_SIGNAL_SIZE)
|
||||||
@ -497,19 +615,36 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
bb.get(addr);
|
bb.get(addr);
|
||||||
int channel = bb.getInt();
|
int channel = bb.getInt();
|
||||||
int status = bb.getInt();
|
int status = bb.getInt();
|
||||||
|
mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
|
||||||
|
mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
|
||||||
String RemoteAddr = convertAddr(addr);
|
String RemoteAddr = convertAddr(addr);
|
||||||
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
|
if (VDBG) Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
|
||||||
+ RemoteAddr + ", channel: " + channel + ", status: " + status);
|
+ RemoteAddr + ", channel: " + channel + ", status: " + status
|
||||||
|
+ " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize);
|
||||||
if(status != 0)
|
if(status != 0)
|
||||||
throw new IOException("Connection failure, status: " + status);
|
throw new IOException("Connection failure, status: " + status);
|
||||||
return RemoteAddr;
|
return RemoteAddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createL2capRxBuffer(){
|
||||||
|
if(mType == TYPE_L2CAP) {
|
||||||
|
// Allocate the buffer to use for reads.
|
||||||
|
if(VDBG) Log.v(TAG, " Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
|
||||||
|
mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);
|
||||||
|
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining());
|
||||||
|
mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request
|
||||||
|
if(VDBG) Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" +
|
||||||
|
mL2capBuffer.remaining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int readAll(InputStream is, byte[] b) throws IOException {
|
private int readAll(InputStream is, byte[] b) throws IOException {
|
||||||
int left = b.length;
|
int left = b.length;
|
||||||
while(left > 0) {
|
while(left > 0) {
|
||||||
int ret = is.read(b, b.length - left, left);
|
int ret = is.read(b, b.length - left, left);
|
||||||
if(ret <= 0)
|
if(ret <= 0)
|
||||||
throw new IOException("read failed, socket might closed or timeout, read ret: " + ret);
|
throw new IOException("read failed, socket might closed or timeout, read ret: "
|
||||||
|
+ ret);
|
||||||
left -= ret;
|
left -= ret;
|
||||||
if(left != 0)
|
if(left != 0)
|
||||||
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
|
Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left) +
|
||||||
@ -526,4 +661,18 @@ public final class BluetoothSocket implements Closeable {
|
|||||||
bb.order(ByteOrder.nativeOrder());
|
bb.order(ByteOrder.nativeOrder());
|
||||||
return bb.getInt();
|
return bb.getInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int fillL2capRxBuffer() throws IOException {
|
||||||
|
mL2capBuffer.rewind();
|
||||||
|
int ret = mSocketIS.read(mL2capBuffer.array());
|
||||||
|
if(ret == -1) {
|
||||||
|
// reached end of stream - return -1
|
||||||
|
mL2capBuffer.limit(0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
mL2capBuffer.limit(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ interface IBluetooth
|
|||||||
int getRemoteClass(in BluetoothDevice device);
|
int getRemoteClass(in BluetoothDevice device);
|
||||||
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
|
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
|
||||||
boolean fetchRemoteUuids(in BluetoothDevice device);
|
boolean fetchRemoteUuids(in BluetoothDevice device);
|
||||||
boolean fetchRemoteMasInstances(in BluetoothDevice device);
|
boolean sdpSearch(in BluetoothDevice device, in ParcelUuid uuid);
|
||||||
|
|
||||||
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
|
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
|
||||||
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
|
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]
|
||||||
|
147
core/java/android/bluetooth/SdpMasRecord.java
Normal file
147
core/java/android/bluetooth/SdpMasRecord.java
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Samsung System LSI
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class SdpMasRecord implements Parcelable {
|
||||||
|
private final int mMasInstanceId;
|
||||||
|
private final int mL2capPsm;
|
||||||
|
private final int mRfcommChannelNumber;
|
||||||
|
private final int mProfileVersion;
|
||||||
|
private final int mSupportedFeatures;
|
||||||
|
private final int mSupportedMessageTypes;
|
||||||
|
private final String mServiceName;
|
||||||
|
public static final class MessageType {
|
||||||
|
public static final int EMAIL = 0x01;
|
||||||
|
public static final int SMS_GSM = 0x02;
|
||||||
|
public static final int SMS_CDMA = 0x04;
|
||||||
|
public static final int MMS = 0x08;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpMasRecord(int mas_instance_id,
|
||||||
|
int l2cap_psm,
|
||||||
|
int rfcomm_channel_number,
|
||||||
|
int profile_version,
|
||||||
|
int supported_features,
|
||||||
|
int supported_message_types,
|
||||||
|
String service_name){
|
||||||
|
this.mMasInstanceId = mas_instance_id;
|
||||||
|
this.mL2capPsm = l2cap_psm;
|
||||||
|
this.mRfcommChannelNumber = rfcomm_channel_number;
|
||||||
|
this.mProfileVersion = profile_version;
|
||||||
|
this.mSupportedFeatures = supported_features;
|
||||||
|
this.mSupportedMessageTypes = supported_message_types;
|
||||||
|
this.mServiceName = service_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpMasRecord(Parcel in){
|
||||||
|
this.mMasInstanceId = in.readInt();
|
||||||
|
this.mL2capPsm = in.readInt();
|
||||||
|
this.mRfcommChannelNumber = in.readInt();
|
||||||
|
this.mProfileVersion = in.readInt();
|
||||||
|
this.mSupportedFeatures = in.readInt();
|
||||||
|
this.mSupportedMessageTypes = in.readInt();
|
||||||
|
this.mServiceName = in.readString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMasInstanceId() {
|
||||||
|
return mMasInstanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getL2capPsm() {
|
||||||
|
return mL2capPsm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRfcommCannelNumber() {
|
||||||
|
return mRfcommChannelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProfileVersion() {
|
||||||
|
return mProfileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSupportedFeatures() {
|
||||||
|
return mSupportedFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSupportedMessageTypes() {
|
||||||
|
return mSupportedMessageTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean msgSupported(int msg) {
|
||||||
|
return (mSupportedMessageTypes & msg) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceName() {
|
||||||
|
return mServiceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
|
||||||
|
dest.writeInt(this.mMasInstanceId);
|
||||||
|
dest.writeInt(this.mL2capPsm);
|
||||||
|
dest.writeInt(this.mRfcommChannelNumber);
|
||||||
|
dest.writeInt(this.mProfileVersion);
|
||||||
|
dest.writeInt(this.mSupportedFeatures);
|
||||||
|
dest.writeInt(this.mSupportedMessageTypes);
|
||||||
|
dest.writeString(this.mServiceName);
|
||||||
|
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String toString(){
|
||||||
|
String ret = "Bluetooth MAS SDP Record:\n";
|
||||||
|
|
||||||
|
if(mMasInstanceId != -1){
|
||||||
|
ret += "Mas Instance Id: " + mMasInstanceId + "\n";
|
||||||
|
}
|
||||||
|
if(mRfcommChannelNumber != -1){
|
||||||
|
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
|
||||||
|
}
|
||||||
|
if(mL2capPsm != -1){
|
||||||
|
ret += "L2CAP PSM: " + mL2capPsm + "\n";
|
||||||
|
}
|
||||||
|
if(mServiceName != null){
|
||||||
|
ret += "Service Name: " + mServiceName + "\n";
|
||||||
|
}
|
||||||
|
if(mProfileVersion != -1){
|
||||||
|
ret += "Profile version: " + mProfileVersion + "\n";
|
||||||
|
}
|
||||||
|
if(mSupportedMessageTypes != -1){
|
||||||
|
ret += "Supported msg types: " + mSupportedMessageTypes + "\n";
|
||||||
|
}
|
||||||
|
if(mSupportedFeatures != -1){
|
||||||
|
ret += "Supported features: " + mSupportedFeatures + "\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
|
public SdpMasRecord createFromParcel(Parcel in) {
|
||||||
|
return new SdpMasRecord(in);
|
||||||
|
}
|
||||||
|
public SdpRecord[] newArray(int size) {
|
||||||
|
return new SdpRecord[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
112
core/java/android/bluetooth/SdpMnsRecord.java
Normal file
112
core/java/android/bluetooth/SdpMnsRecord.java
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Samsung System LSI
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class SdpMnsRecord implements Parcelable {
|
||||||
|
private final int mL2capPsm;
|
||||||
|
private final int mRfcommChannelNumber;
|
||||||
|
private final int mSupportedFeatures;
|
||||||
|
private final int mProfileVersion;
|
||||||
|
private final String mServiceName;
|
||||||
|
|
||||||
|
public SdpMnsRecord(int l2cap_psm,
|
||||||
|
int rfcomm_channel_number,
|
||||||
|
int profile_version,
|
||||||
|
int supported_features,
|
||||||
|
String service_name){
|
||||||
|
this.mL2capPsm = l2cap_psm;
|
||||||
|
this.mRfcommChannelNumber = rfcomm_channel_number;
|
||||||
|
this.mSupportedFeatures = supported_features;
|
||||||
|
this.mServiceName = service_name;
|
||||||
|
this.mProfileVersion = profile_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpMnsRecord(Parcel in){
|
||||||
|
this.mRfcommChannelNumber = in.readInt();
|
||||||
|
this.mL2capPsm = in.readInt();
|
||||||
|
this.mServiceName = in.readString();
|
||||||
|
this.mSupportedFeatures = in.readInt();
|
||||||
|
this.mProfileVersion = in.readInt();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getL2capPsm() {
|
||||||
|
return mL2capPsm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRfcommChannelNumber() {
|
||||||
|
return mRfcommChannelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSupportedFeatures() {
|
||||||
|
return mSupportedFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceName() {
|
||||||
|
return mServiceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProfileVersion() {
|
||||||
|
return mProfileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mRfcommChannelNumber);
|
||||||
|
dest.writeInt(mL2capPsm);
|
||||||
|
dest.writeString(mServiceName);
|
||||||
|
dest.writeInt(mSupportedFeatures);
|
||||||
|
dest.writeInt(mProfileVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(){
|
||||||
|
String ret = "Bluetooth MNS SDP Record:\n";
|
||||||
|
|
||||||
|
if(mRfcommChannelNumber != -1){
|
||||||
|
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
|
||||||
|
}
|
||||||
|
if(mL2capPsm != -1){
|
||||||
|
ret += "L2CAP PSM: " + mL2capPsm + "\n";
|
||||||
|
}
|
||||||
|
if(mServiceName != null){
|
||||||
|
ret += "Service Name: " + mServiceName + "\n";
|
||||||
|
}
|
||||||
|
if(mSupportedFeatures != -1){
|
||||||
|
ret += "Supported features: " + mSupportedFeatures + "\n";
|
||||||
|
}
|
||||||
|
if(mProfileVersion != -1){
|
||||||
|
ret += "Profile_version: " + mProfileVersion+"\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
|
public SdpMnsRecord createFromParcel(Parcel in) {
|
||||||
|
return new SdpMnsRecord(in);
|
||||||
|
}
|
||||||
|
public SdpMnsRecord[] newArray(int size) {
|
||||||
|
return new SdpMnsRecord[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
118
core/java/android/bluetooth/SdpOppOpsRecord.java
Normal file
118
core/java/android/bluetooth/SdpOppOpsRecord.java
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Samsung System LSI
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data representation of a Object Push Profile Server side SDP record.
|
||||||
|
*/
|
||||||
|
/** @hide */
|
||||||
|
public class SdpOppOpsRecord implements Parcelable {
|
||||||
|
|
||||||
|
private final String mServiceName;
|
||||||
|
private final int mRfcommChannel;
|
||||||
|
private final int mL2capPsm;
|
||||||
|
private final int mProfileVersion;
|
||||||
|
private final byte[] mFormatsList;
|
||||||
|
|
||||||
|
public SdpOppOpsRecord(String serviceName, int rfcommChannel,
|
||||||
|
int l2capPsm, int version, byte[] formatsList) {
|
||||||
|
super();
|
||||||
|
this.mServiceName = serviceName;
|
||||||
|
this.mRfcommChannel = rfcommChannel;
|
||||||
|
this.mL2capPsm = l2capPsm;
|
||||||
|
this.mProfileVersion = version;
|
||||||
|
this.mFormatsList = formatsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceName() {
|
||||||
|
return mServiceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRfcommChannel() {
|
||||||
|
return mRfcommChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getL2capPsm() {
|
||||||
|
return mL2capPsm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProfileVersion() {
|
||||||
|
return mProfileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getFormatsList() {
|
||||||
|
return mFormatsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
/* No special objects */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpOppOpsRecord(Parcel in){
|
||||||
|
this.mRfcommChannel = in.readInt();
|
||||||
|
this.mL2capPsm = in.readInt();
|
||||||
|
this.mProfileVersion = in.readInt();
|
||||||
|
this.mServiceName = in.readString();
|
||||||
|
int arrayLength = in.readInt();
|
||||||
|
if(arrayLength > 0) {
|
||||||
|
byte[] bytes = new byte[arrayLength];
|
||||||
|
in.readByteArray(bytes);
|
||||||
|
this.mFormatsList = bytes;
|
||||||
|
} else {
|
||||||
|
this.mFormatsList = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mRfcommChannel);
|
||||||
|
dest.writeInt(mL2capPsm);
|
||||||
|
dest.writeInt(mProfileVersion);
|
||||||
|
dest.writeString(mServiceName);
|
||||||
|
if(mFormatsList!= null && mFormatsList.length > 0) {
|
||||||
|
dest.writeInt(mFormatsList.length);
|
||||||
|
dest.writeByteArray(mFormatsList);
|
||||||
|
} else {
|
||||||
|
dest.writeInt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(){
|
||||||
|
StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n");
|
||||||
|
sb.append(" RFCOMM Chan Number: ").append(mRfcommChannel);
|
||||||
|
sb.append("\n L2CAP PSM: ").append(mL2capPsm);
|
||||||
|
sb.append("\n Profile version: ").append(mProfileVersion);
|
||||||
|
sb.append("\n Service Name: ").append(mServiceName);
|
||||||
|
sb.append("\n Formats List: ").append(Arrays.toString(mFormatsList));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
|
public SdpOppOpsRecord createFromParcel(Parcel in) {
|
||||||
|
return new SdpOppOpsRecord(in);
|
||||||
|
}
|
||||||
|
public SdpOppOpsRecord[] newArray(int size) {
|
||||||
|
return new SdpOppOpsRecord[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
125
core/java/android/bluetooth/SdpPseRecord.java
Normal file
125
core/java/android/bluetooth/SdpPseRecord.java
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Samsung System LSI
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class SdpPseRecord implements Parcelable {
|
||||||
|
private final int mL2capPsm;
|
||||||
|
private final int mRfcommChannelNumber;
|
||||||
|
private final int mProfileVersion;
|
||||||
|
private final int mSupportedFeatures;
|
||||||
|
private final int mSupportedRepositories;
|
||||||
|
private final String mServiceName;
|
||||||
|
|
||||||
|
public SdpPseRecord(int l2cap_psm,
|
||||||
|
int rfcomm_channel_number,
|
||||||
|
int profile_version,
|
||||||
|
int supported_features,
|
||||||
|
int supported_repositories,
|
||||||
|
String service_name){
|
||||||
|
this.mL2capPsm = l2cap_psm;
|
||||||
|
this.mRfcommChannelNumber = rfcomm_channel_number;
|
||||||
|
this.mProfileVersion = profile_version;
|
||||||
|
this.mSupportedFeatures = supported_features;
|
||||||
|
this.mSupportedRepositories = supported_repositories;
|
||||||
|
this.mServiceName = service_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpPseRecord(Parcel in){
|
||||||
|
this.mRfcommChannelNumber = in.readInt();
|
||||||
|
this.mL2capPsm = in.readInt();
|
||||||
|
this.mProfileVersion = in.readInt();
|
||||||
|
this.mSupportedFeatures = in.readInt();
|
||||||
|
this.mSupportedRepositories = in.readInt();
|
||||||
|
this.mServiceName = in.readString();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getL2capPsm() {
|
||||||
|
return mL2capPsm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRfcommChannelNumber() {
|
||||||
|
return mRfcommChannelNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSupportedFeatures() {
|
||||||
|
return mSupportedFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getServiceName() {
|
||||||
|
return mServiceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProfileVersion() {
|
||||||
|
return mProfileVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSupportedRepositories() {
|
||||||
|
return mSupportedRepositories;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(mRfcommChannelNumber);
|
||||||
|
dest.writeInt(mL2capPsm);
|
||||||
|
dest.writeInt(mProfileVersion);
|
||||||
|
dest.writeInt(mSupportedFeatures);
|
||||||
|
dest.writeInt(mSupportedRepositories);
|
||||||
|
dest.writeString(mServiceName);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(){
|
||||||
|
String ret = "Bluetooth MNS SDP Record:\n";
|
||||||
|
|
||||||
|
if(mRfcommChannelNumber != -1){
|
||||||
|
ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
|
||||||
|
}
|
||||||
|
if(mL2capPsm != -1){
|
||||||
|
ret += "L2CAP PSM: " + mL2capPsm + "\n";
|
||||||
|
}
|
||||||
|
if(mProfileVersion != -1){
|
||||||
|
ret += "profile version: " + mProfileVersion + "\n";
|
||||||
|
}
|
||||||
|
if(mServiceName != null){
|
||||||
|
ret += "Service Name: " + mServiceName + "\n";
|
||||||
|
}
|
||||||
|
if(mSupportedFeatures != -1){
|
||||||
|
ret += "Supported features: " + mSupportedFeatures + "\n";
|
||||||
|
}
|
||||||
|
if(mSupportedRepositories != -1){
|
||||||
|
ret += "Supported repositories: " + mSupportedRepositories + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
|
public SdpPseRecord createFromParcel(Parcel in) {
|
||||||
|
return new SdpPseRecord(in);
|
||||||
|
}
|
||||||
|
public SdpPseRecord[] newArray(int size) {
|
||||||
|
return new SdpPseRecord[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
76
core/java/android/bluetooth/SdpRecord.java
Normal file
76
core/java/android/bluetooth/SdpRecord.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 Samsung System LSI
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public class SdpRecord implements Parcelable{
|
||||||
|
|
||||||
|
private final byte[] mRawData;
|
||||||
|
private final int mRawSize;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData)
|
||||||
|
+ ", rawSize=" + mRawSize + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpRecord(int size_record, byte[] record){
|
||||||
|
this.mRawData = record;
|
||||||
|
this.mRawSize = size_record;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpRecord(Parcel in){
|
||||||
|
this.mRawSize = in.readInt();
|
||||||
|
this.mRawData = new byte[mRawSize];
|
||||||
|
in.readByteArray(this.mRawData);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeInt(this.mRawSize);
|
||||||
|
dest.writeByteArray(this.mRawData);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||||
|
public SdpRecord createFromParcel(Parcel in) {
|
||||||
|
return new SdpRecord(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SdpRecord[] newArray(int size) {
|
||||||
|
return new SdpRecord[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
return mRawData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRawSize() {
|
||||||
|
return mRawSize;
|
||||||
|
}
|
||||||
|
}
|
@ -7,3 +7,14 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
|||||||
LOCAL_MODULE:= javax.obex
|
LOCAL_MODULE:= javax.obex
|
||||||
|
|
||||||
include $(BUILD_JAVA_LIBRARY)
|
include $(BUILD_JAVA_LIBRARY)
|
||||||
|
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := optional
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||||
|
|
||||||
|
LOCAL_MODULE:= javax.obexstatic
|
||||||
|
|
||||||
|
include $(BUILD_STATIC_JAVA_LIBRARY)
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2014 The Android Open Source Project
|
* Copyright (c) 2015 The Android Open Source Project
|
||||||
|
* Copyright (C) 2015 Samsung LSI
|
||||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -40,6 +41,8 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements the <code>Operation</code> interface. It will read and
|
* This class implements the <code>Operation</code> interface. It will read and
|
||||||
* write data via puts and gets.
|
* write data via puts and gets.
|
||||||
@ -47,6 +50,10 @@ import java.io.ByteArrayOutputStream;
|
|||||||
*/
|
*/
|
||||||
public final class ClientOperation implements Operation, BaseStream {
|
public final class ClientOperation implements Operation, BaseStream {
|
||||||
|
|
||||||
|
private static final String TAG = "ClientOperation";
|
||||||
|
|
||||||
|
private static final boolean V = ObexHelper.VDBG;
|
||||||
|
|
||||||
private ClientSession mParent;
|
private ClientSession mParent;
|
||||||
|
|
||||||
private boolean mInputOpen;
|
private boolean mInputOpen;
|
||||||
@ -75,6 +82,19 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
private boolean mEndOfBodySent;
|
private boolean mEndOfBodySent;
|
||||||
|
|
||||||
|
private boolean mSendBodyHeader = true;
|
||||||
|
// A latch - when triggered, there is not way back ;-)
|
||||||
|
private boolean mSrmActive = false;
|
||||||
|
|
||||||
|
// Assume SRM disabled - until support is confirmed
|
||||||
|
// by the server
|
||||||
|
private boolean mSrmEnabled = false;
|
||||||
|
// keep waiting until final-bit is received in request
|
||||||
|
// to handle the case where the SRM enable header is in
|
||||||
|
// a different OBEX packet than the SRMP header.
|
||||||
|
private boolean mSrmWaitingForRemote = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new OperationImpl to read and write data to a server
|
* Creates new OperationImpl to read and write data to a server
|
||||||
* @param maxSize the maximum packet size
|
* @param maxSize the maximum packet size
|
||||||
@ -164,7 +184,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
* Since we are not sending any headers or returning any headers then
|
* Since we are not sending any headers or returning any headers then
|
||||||
* we just need to write and read the same bytes
|
* we just need to write and read the same bytes
|
||||||
*/
|
*/
|
||||||
mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null);
|
mParent.sendRequest(ObexHelper.OBEX_OPCODE_ABORT, null, mReplyHeader, null, false);
|
||||||
|
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
|
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_OK) {
|
||||||
throw new IOException("Invalid response code from server");
|
throw new IOException("Invalid response code from server");
|
||||||
@ -215,6 +235,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
try {
|
try {
|
||||||
return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
|
return (String)mReplyHeader.getHeader(HeaderSet.TYPE);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
if(V) Log.d(TAG, "Exception occured - returning null",e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -236,6 +257,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
return temp.longValue();
|
return temp.longValue();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
if(V) Log.d(TAG,"Exception occured - returning -1",e);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -408,7 +430,9 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a request to the client of the specified type
|
* Sends a request to the client of the specified type.
|
||||||
|
* This function will enable SRM and set SRM active if the server
|
||||||
|
* response allows this.
|
||||||
* @param opCode the request code to send to the client
|
* @param opCode the request code to send to the client
|
||||||
* @return <code>true</code> if there is more data to send;
|
* @return <code>true</code> if there is more data to send;
|
||||||
* <code>false</code> if there is no more data to send
|
* <code>false</code> if there is no more data to send
|
||||||
@ -431,13 +455,16 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
* length, but it is a waste of resources if we can't send much of
|
* length, but it is a waste of resources if we can't send much of
|
||||||
* the body.
|
* the body.
|
||||||
*/
|
*/
|
||||||
if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length) > mMaxPacketSize) {
|
final int MINIMUM_BODY_LENGTH = 3;
|
||||||
|
if ((ObexHelper.BASE_PACKET_LENGTH + headerArray.length + MINIMUM_BODY_LENGTH)
|
||||||
|
> mMaxPacketSize) {
|
||||||
int end = 0;
|
int end = 0;
|
||||||
int start = 0;
|
int start = 0;
|
||||||
// split & send the headerArray in multiple packets.
|
// split & send the headerArray in multiple packets.
|
||||||
|
|
||||||
while (end != headerArray.length) {
|
while (end != headerArray.length) {
|
||||||
//split the headerArray
|
//split the headerArray
|
||||||
|
|
||||||
end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
|
end = ObexHelper.findHeaderEnd(headerArray, start, mMaxPacketSize
|
||||||
- ObexHelper.BASE_PACKET_LENGTH);
|
- ObexHelper.BASE_PACKET_LENGTH);
|
||||||
// can not split
|
// can not split
|
||||||
@ -459,7 +486,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
byte[] sendHeader = new byte[end - start];
|
byte[] sendHeader = new byte[end - start];
|
||||||
System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
|
System.arraycopy(headerArray, start, sendHeader, 0, sendHeader.length);
|
||||||
if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput)) {
|
if (!mParent.sendRequest(opCode, sendHeader, mReplyHeader, mPrivateInput, false)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,12 +497,20 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
start = end;
|
start = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable SRM if it should be enabled
|
||||||
|
checkForSrm();
|
||||||
|
|
||||||
if (bodyLength > 0) {
|
if (bodyLength > 0) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/* All headers will fit into a single package */
|
||||||
|
if(mSendBodyHeader == false) {
|
||||||
|
/* As we are not to send any body data, set the FINAL_BIT */
|
||||||
|
opCode |= ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK;
|
||||||
|
}
|
||||||
out.write(headerArray);
|
out.write(headerArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,11 +534,11 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
* (End of Body) otherwise, we need to send 0x48 (Body)
|
* (End of Body) otherwise, we need to send 0x48 (Body)
|
||||||
*/
|
*/
|
||||||
if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
|
if ((mPrivateOutput.isClosed()) && (!returnValue) && (!mEndOfBodySent)
|
||||||
&& ((opCode & 0x80) != 0)) {
|
&& ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) != 0)) {
|
||||||
out.write(0x49);
|
out.write(HeaderSet.END_OF_BODY);
|
||||||
mEndOfBodySent = true;
|
mEndOfBodySent = true;
|
||||||
} else {
|
} else {
|
||||||
out.write(0x48);
|
out.write(HeaderSet.BODY);
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyLength += 3;
|
bodyLength += 3;
|
||||||
@ -517,12 +552,11 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
|
if (mPrivateOutputOpen && bodyLength <= 0 && !mEndOfBodySent) {
|
||||||
// only 0x82 or 0x83 can send 0x49
|
// only 0x82 or 0x83 can send 0x49
|
||||||
if ((opCode & 0x80) == 0) {
|
if ((opCode & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
|
||||||
out.write(0x48);
|
out.write(HeaderSet.BODY);
|
||||||
} else {
|
} else {
|
||||||
out.write(0x49);
|
out.write(HeaderSet.END_OF_BODY);
|
||||||
mEndOfBodySent = true;
|
mEndOfBodySent = true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyLength = 3;
|
bodyLength = 3;
|
||||||
@ -531,15 +565,20 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (out.size() == 0) {
|
if (out.size() == 0) {
|
||||||
if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput)) {
|
if (!mParent.sendRequest(opCode, null, mReplyHeader, mPrivateInput, mSrmActive)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Enable SRM if it should be enabled
|
||||||
|
checkForSrm();
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
if ((out.size() > 0)
|
if ((out.size() > 0)
|
||||||
&& (!mParent.sendRequest(opCode, out.toByteArray(), mReplyHeader, mPrivateInput))) {
|
&& (!mParent.sendRequest(opCode, out.toByteArray(),
|
||||||
|
mReplyHeader, mPrivateInput, mSrmActive))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Enable SRM if it should be enabled
|
||||||
|
checkForSrm();
|
||||||
|
|
||||||
// send all of the output data in 0x48,
|
// send all of the output data in 0x48,
|
||||||
// send 0x49 with empty body
|
// send 0x49 with empty body
|
||||||
@ -549,6 +588,35 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkForSrm() throws IOException {
|
||||||
|
Byte srmMode = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||||
|
if(mParent.isSrmSupported() == true && srmMode != null
|
||||||
|
&& srmMode == ObexHelper.OBEX_SRM_ENABLE) {
|
||||||
|
mSrmEnabled = true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Call this only when a complete obex packet have been received.
|
||||||
|
* (This is not optimal, but the current design is not really suited to
|
||||||
|
* the way SRM is specified.)
|
||||||
|
* The BT usage of SRM is not really safe - it assumes that the SRMP will fit
|
||||||
|
* into every OBEX packet, hence if another header occupies the entire packet,
|
||||||
|
* the scheme will not work - unlikely though.
|
||||||
|
*/
|
||||||
|
if(mSrmEnabled) {
|
||||||
|
mSrmWaitingForRemote = false;
|
||||||
|
Byte srmp = (Byte)mReplyHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||||
|
if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
|
||||||
|
mSrmWaitingForRemote = true;
|
||||||
|
// Clear the wait header, as the absence of the header in the next packet
|
||||||
|
// indicates don't wait anymore.
|
||||||
|
mReplyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if((mSrmWaitingForRemote == false) && (mSrmEnabled == true)) {
|
||||||
|
mSrmActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method starts the processing thread results. It will send the
|
* This method starts the processing thread results. It will send the
|
||||||
* initial request. If the response takes more then one packet, a thread
|
* initial request. If the response takes more then one packet, a thread
|
||||||
@ -564,40 +632,35 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
if (mGetOperation) {
|
if (mGetOperation) {
|
||||||
if (!mOperationDone) {
|
if (!mOperationDone) {
|
||||||
if (!mGetFinalFlag) {
|
|
||||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||||
more = sendRequest(0x03);
|
more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||||
}
|
}
|
||||||
|
// For GET we need to loop until all headers have been sent,
|
||||||
|
// And then we wait for the first continue package with the
|
||||||
|
// reply.
|
||||||
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
|
||||||
|
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||||
}
|
}
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mOperationDone = true;
|
mOperationDone = true;
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
more = sendRequest(0x83);
|
checkForSrm();
|
||||||
|
|
||||||
if (more) {
|
|
||||||
throw new IOException("FINAL_GET forced but data did not fit into single packet!");
|
|
||||||
}
|
|
||||||
|
|
||||||
mOperationDone = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// PUT operation
|
||||||
if (!mOperationDone) {
|
if (!mOperationDone) {
|
||||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||||
more = sendRequest(0x02);
|
more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mParent.sendRequest(0x82, null, mReplyHeader, mPrivateInput);
|
mParent.sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL,
|
||||||
|
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
@ -617,15 +680,21 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
|
public synchronized boolean continueOperation(boolean sendEmpty, boolean inStream)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
|
// One path to the first put operation - the other one does not need to
|
||||||
|
// handle SRM, as all will fit into one packet.
|
||||||
|
|
||||||
if (mGetOperation) {
|
if (mGetOperation) {
|
||||||
if ((inStream) && (!mOperationDone)) {
|
if ((inStream) && (!mOperationDone)) {
|
||||||
// to deal with inputstream in get operation
|
// to deal with inputstream in get operation
|
||||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL,
|
||||||
|
null, mReplyHeader, mPrivateInput, mSrmActive);
|
||||||
/*
|
/*
|
||||||
* Determine if that was not the last packet in the operation
|
* Determine if that was not the last packet in the operation
|
||||||
*/
|
*/
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mOperationDone = true;
|
mOperationDone = true;
|
||||||
|
} else {
|
||||||
|
checkForSrm();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -636,16 +705,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
if (mPrivateInput == null) {
|
if (mPrivateInput == null) {
|
||||||
mPrivateInput = new PrivateInputStream(this);
|
mPrivateInput = new PrivateInputStream(this);
|
||||||
}
|
}
|
||||||
|
sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||||
if (!mGetFinalFlag) {
|
|
||||||
sendRequest(0x03);
|
|
||||||
} else {
|
|
||||||
sendRequest(0x83);
|
|
||||||
|
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
|
||||||
mOperationDone = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} else if (mOperationDone) {
|
} else if (mOperationDone) {
|
||||||
@ -653,12 +713,13 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// PUT operation
|
||||||
if ((!inStream) && (!mOperationDone)) {
|
if ((!inStream) && (!mOperationDone)) {
|
||||||
// to deal with outputstream in put operation
|
// to deal with outputstream in put operation
|
||||||
if (mReplyHeader.responseCode == -1) {
|
if (mReplyHeader.responseCode == -1) {
|
||||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||||
}
|
}
|
||||||
sendRequest(0x02);
|
sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||||
return true;
|
return true;
|
||||||
} else if ((inStream) && (!mOperationDone)) {
|
} else if ((inStream) && (!mOperationDone)) {
|
||||||
// How to deal with inputstream in put operation ?
|
// How to deal with inputstream in put operation ?
|
||||||
@ -696,7 +757,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||||
more = sendRequest(0x02);
|
more = sendRequest(ObexHelper.OBEX_OPCODE_PUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -706,7 +767,7 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
*/
|
*/
|
||||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
|
|
||||||
sendRequest(0x82);
|
sendRequest(ObexHelper.OBEX_OPCODE_PUT_FINAL);
|
||||||
}
|
}
|
||||||
mOperationDone = true;
|
mOperationDone = true;
|
||||||
} else if ((inStream) && (mOperationDone)) {
|
} else if ((inStream) && (mOperationDone)) {
|
||||||
@ -724,12 +785,14 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
if (!sendRequest(0x83)) {
|
if (!sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
while (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
|
mParent.sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL, null,
|
||||||
|
mReplyHeader, mPrivateInput, false);
|
||||||
|
// Regardless of the SRM state, wait for the response.
|
||||||
}
|
}
|
||||||
mOperationDone = true;
|
mOperationDone = true;
|
||||||
} else if ((!inStream) && (!mOperationDone)) {
|
} else if ((!inStream) && (!mOperationDone)) {
|
||||||
@ -752,9 +815,9 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
|
||||||
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
|
||||||
more = sendRequest(0x03);
|
more = sendRequest(ObexHelper.OBEX_OPCODE_GET);
|
||||||
}
|
}
|
||||||
sendRequest(0x83);
|
sendRequest(ObexHelper.OBEX_OPCODE_GET_FINAL);
|
||||||
// parent.sendRequest(0x83, null, replyHeaders, privateInput);
|
// parent.sendRequest(0x83, null, replyHeaders, privateInput);
|
||||||
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
mOperationDone = true;
|
mOperationDone = true;
|
||||||
@ -764,5 +827,6 @@ public final class ClientOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void noBodyHeader(){
|
public void noBodyHeader(){
|
||||||
|
mSendBodyHeader = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (c) 2015 The Android Open Source Project
|
||||||
|
* Copyright (C) 2015 Samsung LSI
|
||||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -37,12 +39,16 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class in an implementation of the OBEX ClientSession.
|
* This class in an implementation of the OBEX ClientSession.
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public final class ClientSession extends ObexSession {
|
public final class ClientSession extends ObexSession {
|
||||||
|
|
||||||
|
private static final String TAG = "ClientSession";
|
||||||
|
|
||||||
private boolean mOpen;
|
private boolean mOpen;
|
||||||
|
|
||||||
// Determines if an OBEX layer connection has been established
|
// Determines if an OBEX layer connection has been established
|
||||||
@ -51,10 +57,10 @@ public final class ClientSession extends ObexSession {
|
|||||||
private byte[] mConnectionId = null;
|
private byte[] mConnectionId = null;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The max Packet size must be at least 256 according to the OBEX
|
* The max Packet size must be at least 255 according to the OBEX
|
||||||
* specification.
|
* specification.
|
||||||
*/
|
*/
|
||||||
private int maxPacketSize = 256;
|
private int mMaxTxPacketSize = ObexHelper.LOWER_LIMIT_MAX_PACKET_SIZE;
|
||||||
|
|
||||||
private boolean mRequestActive;
|
private boolean mRequestActive;
|
||||||
|
|
||||||
@ -62,11 +68,33 @@ public final class ClientSession extends ObexSession {
|
|||||||
|
|
||||||
private final OutputStream mOutput;
|
private final OutputStream mOutput;
|
||||||
|
|
||||||
|
private final boolean mLocalSrmSupported;
|
||||||
|
|
||||||
|
private final ObexTransport mTransport;
|
||||||
|
|
||||||
public ClientSession(final ObexTransport trans) throws IOException {
|
public ClientSession(final ObexTransport trans) throws IOException {
|
||||||
mInput = trans.openInputStream();
|
mInput = trans.openInputStream();
|
||||||
mOutput = trans.openOutputStream();
|
mOutput = trans.openOutputStream();
|
||||||
mOpen = true;
|
mOpen = true;
|
||||||
mRequestActive = false;
|
mRequestActive = false;
|
||||||
|
mLocalSrmSupported = trans.isSrmSupported();
|
||||||
|
mTransport = trans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ClientSession
|
||||||
|
* @param trans The transport to use for OBEX transactions
|
||||||
|
* @param supportsSrm True if Single Response Mode should be used e.g. if the
|
||||||
|
* supplied transport is a TCP or l2cap channel.
|
||||||
|
* @throws IOException if it occurs while opening the transport streams.
|
||||||
|
*/
|
||||||
|
public ClientSession(final ObexTransport trans, final boolean supportsSrm) throws IOException {
|
||||||
|
mInput = trans.openInputStream();
|
||||||
|
mOutput = trans.openOutputStream();
|
||||||
|
mOpen = true;
|
||||||
|
mRequestActive = false;
|
||||||
|
mLocalSrmSupported = supportsSrm;
|
||||||
|
mTransport = trans;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HeaderSet connect(final HeaderSet header) throws IOException {
|
public HeaderSet connect(final HeaderSet header) throws IOException {
|
||||||
@ -98,23 +126,25 @@ public final class ClientSession extends ObexSession {
|
|||||||
* Byte 7 to n: headers
|
* Byte 7 to n: headers
|
||||||
*/
|
*/
|
||||||
byte[] requestPacket = new byte[totalLength];
|
byte[] requestPacket = new byte[totalLength];
|
||||||
|
int maxRxPacketSize = ObexHelper.getMaxRxPacketSize(mTransport);
|
||||||
// We just need to start at byte 3 since the sendRequest() method will
|
// We just need to start at byte 3 since the sendRequest() method will
|
||||||
// handle the length and 0x80.
|
// handle the length and 0x80.
|
||||||
requestPacket[0] = (byte)0x10;
|
requestPacket[0] = (byte)0x10;
|
||||||
requestPacket[1] = (byte)0x00;
|
requestPacket[1] = (byte)0x00;
|
||||||
requestPacket[2] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
|
requestPacket[2] = (byte)(maxRxPacketSize >> 8);
|
||||||
requestPacket[3] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
|
requestPacket[3] = (byte)(maxRxPacketSize & 0xFF);
|
||||||
if (head != null) {
|
if (head != null) {
|
||||||
System.arraycopy(head, 0, requestPacket, 4, head.length);
|
System.arraycopy(head, 0, requestPacket, 4, head.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check with local max packet size
|
// Since we are not yet connected, the peer max packet size is unknown,
|
||||||
|
// hence we are only guaranteed the server will use the first 7 bytes.
|
||||||
if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if ((requestPacket.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||||
throw new IOException("Packet size exceeds max packet size");
|
throw new IOException("Packet size exceeds max packet size for connect");
|
||||||
}
|
}
|
||||||
|
|
||||||
HeaderSet returnHeaderSet = new HeaderSet();
|
HeaderSet returnHeaderSet = new HeaderSet();
|
||||||
sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null);
|
sendRequest(ObexHelper.OBEX_OPCODE_CONNECT, requestPacket, returnHeaderSet, null, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read the response from the OBEX server.
|
* Read the response from the OBEX server.
|
||||||
@ -158,7 +188,18 @@ public final class ClientSession extends ObexSession {
|
|||||||
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClientOperation(maxPacketSize, this, head, true);
|
if(mLocalSrmSupported) {
|
||||||
|
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
|
||||||
|
/* TODO: Consider creating an interface to get the wait state.
|
||||||
|
* On an android system, I cannot see when this is to be used.
|
||||||
|
* except perhaps if we are to wait for user accept on a push message.
|
||||||
|
if(getLocalWaitState()) {
|
||||||
|
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClientOperation(mMaxTxPacketSize, this, head, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -202,7 +243,7 @@ public final class ClientSession extends ObexSession {
|
|||||||
}
|
}
|
||||||
head = ObexHelper.createHeader(header, false);
|
head = ObexHelper.createHeader(header, false);
|
||||||
|
|
||||||
if ((head.length + 3) > maxPacketSize) {
|
if ((head.length + 3) > mMaxTxPacketSize) {
|
||||||
throw new IOException("Packet size exceeds max packet size");
|
throw new IOException("Packet size exceeds max packet size");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -215,7 +256,7 @@ public final class ClientSession extends ObexSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HeaderSet returnHeaderSet = new HeaderSet();
|
HeaderSet returnHeaderSet = new HeaderSet();
|
||||||
sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null);
|
sendRequest(ObexHelper.OBEX_OPCODE_DISCONNECT, head, returnHeaderSet, null, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An OBEX DISCONNECT reply from the server:
|
* An OBEX DISCONNECT reply from the server:
|
||||||
@ -269,7 +310,16 @@ public final class ClientSession extends ObexSession {
|
|||||||
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
System.arraycopy(mConnectionId, 0, head.mConnectionID, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ClientOperation(maxPacketSize, this, head, false);
|
if(mLocalSrmSupported) {
|
||||||
|
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, ObexHelper.OBEX_SRM_ENABLE);
|
||||||
|
/* TODO: Consider creating an interface to get the wait state.
|
||||||
|
* On an android system, I cannot see when this is to be used.
|
||||||
|
if(getLocalWaitState()) {
|
||||||
|
head.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, ObexHelper.OBEX_SRMP_WAIT);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
return new ClientOperation(mMaxTxPacketSize, this, head, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticator(Authenticator auth) throws IOException {
|
public void setAuthenticator(Authenticator auth) throws IOException {
|
||||||
@ -314,7 +364,7 @@ public final class ClientSession extends ObexSession {
|
|||||||
head = ObexHelper.createHeader(headset, false);
|
head = ObexHelper.createHeader(headset, false);
|
||||||
totalLength += head.length;
|
totalLength += head.length;
|
||||||
|
|
||||||
if (totalLength > maxPacketSize) {
|
if (totalLength > mMaxTxPacketSize) {
|
||||||
throw new IOException("Packet size exceeds max packet size");
|
throw new IOException("Packet size exceeds max packet size");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,7 +398,7 @@ public final class ClientSession extends ObexSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HeaderSet returnHeaderSet = new HeaderSet();
|
HeaderSet returnHeaderSet = new HeaderSet();
|
||||||
sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null);
|
sendRequest(ObexHelper.OBEX_OPCODE_SETPATH, packet, returnHeaderSet, null, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* An OBEX SETPATH reply from the server:
|
* An OBEX SETPATH reply from the server:
|
||||||
@ -400,20 +450,40 @@ public final class ClientSession extends ObexSession {
|
|||||||
* @param head the headers to send to the client
|
* @param head the headers to send to the client
|
||||||
* @param header the header object to update with the response
|
* @param header the header object to update with the response
|
||||||
* @param privateInput the input stream used by the Operation object; null
|
* @param privateInput the input stream used by the Operation object; null
|
||||||
* if this is called on a CONNECT, SETPATH or DISCONNECT return
|
* if this is called on a CONNECT, SETPATH or DISCONNECT
|
||||||
|
* @return
|
||||||
* <code>true</code> if the operation completed successfully;
|
* <code>true</code> if the operation completed successfully;
|
||||||
* <code>false</code> if an authentication response failed to pass
|
* <code>false</code> if an authentication response failed to pass
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
|
public boolean sendRequest(int opCode, byte[] head, HeaderSet header,
|
||||||
PrivateInputStream privateInput) throws IOException {
|
PrivateInputStream privateInput, boolean srmActive) throws IOException {
|
||||||
//check header length with local max size
|
//check header length with local max size
|
||||||
if (head != null) {
|
if (head != null) {
|
||||||
if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if ((head.length + 3) > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||||
|
// TODO: This is an implementation limit - not a specification requirement.
|
||||||
throw new IOException("header too large ");
|
throw new IOException("header too large ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean skipSend = false;
|
||||||
|
boolean skipReceive = false;
|
||||||
|
if (srmActive == true) {
|
||||||
|
if (opCode == ObexHelper.OBEX_OPCODE_PUT) {
|
||||||
|
// we are in the middle of a SRM PUT operation, don't expect a continue.
|
||||||
|
skipReceive = true;
|
||||||
|
} else if (opCode == ObexHelper.OBEX_OPCODE_GET) {
|
||||||
|
// We are still sending the get request, send, but don't expect continue
|
||||||
|
// until the request is transfered (the final bit is set)
|
||||||
|
skipReceive = true;
|
||||||
|
} else if (opCode == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||||
|
// All done sending the request, expect data from the server, without
|
||||||
|
// sending continue.
|
||||||
|
skipSend = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
int bytesReceived;
|
int bytesReceived;
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
out.write((byte)opCode);
|
out.write((byte)opCode);
|
||||||
@ -428,15 +498,22 @@ public final class ClientSession extends ObexSession {
|
|||||||
out.write(head);
|
out.write(head);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!skipSend) {
|
||||||
// Write the request to the output stream and flush the stream
|
// Write the request to the output stream and flush the stream
|
||||||
mOutput.write(out.toByteArray());
|
mOutput.write(out.toByteArray());
|
||||||
|
// TODO: is this really needed? if this flush is implemented
|
||||||
|
// correctly, we will get a gap between each obex packet.
|
||||||
|
// which is kind of the idea behind SRM to avoid.
|
||||||
|
// Consider offloading to another thread (async action)
|
||||||
mOutput.flush();
|
mOutput.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipReceive) {
|
||||||
header.responseCode = mInput.read();
|
header.responseCode = mInput.read();
|
||||||
|
|
||||||
int length = ((mInput.read() << 8) | (mInput.read()));
|
int length = ((mInput.read() << 8) | (mInput.read()));
|
||||||
|
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
throw new IOException("Packet received exceeds packet size limit");
|
throw new IOException("Packet received exceeds packet size limit");
|
||||||
}
|
}
|
||||||
if (length > ObexHelper.BASE_PACKET_LENGTH) {
|
if (length > ObexHelper.BASE_PACKET_LENGTH) {
|
||||||
@ -446,11 +523,22 @@ public final class ClientSession extends ObexSession {
|
|||||||
int version = mInput.read();
|
int version = mInput.read();
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
int flags = mInput.read();
|
int flags = mInput.read();
|
||||||
maxPacketSize = (mInput.read() << 8) + mInput.read();
|
mMaxTxPacketSize = (mInput.read() << 8) + mInput.read();
|
||||||
|
|
||||||
//check with local max size
|
//check with local max size
|
||||||
if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
|
if (mMaxTxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
|
||||||
maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
|
mMaxTxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check with transport maximum size
|
||||||
|
if(mMaxTxPacketSize > ObexHelper.getMaxTxPacketSize(mTransport)) {
|
||||||
|
// To increase this size, increase the buffer size in L2CAP layer
|
||||||
|
// in Bluedroid.
|
||||||
|
Log.w(TAG, "An OBEX packet size of " + mMaxTxPacketSize + "was"
|
||||||
|
+ " requested. Transport only allows: "
|
||||||
|
+ ObexHelper.getMaxTxPacketSize(mTransport)
|
||||||
|
+ " Lowering limit to this value.");
|
||||||
|
mMaxTxPacketSize = ObexHelper.getMaxTxPacketSize(mTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (length > 7) {
|
if (length > 7) {
|
||||||
@ -507,7 +595,8 @@ public final class ClientSession extends ObexSession {
|
|||||||
byte[] sendHeaders = new byte[out.size() - 3];
|
byte[] sendHeaders = new byte[out.size() - 3];
|
||||||
System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
|
System.arraycopy(out.toByteArray(), 3, sendHeaders, 0, sendHeaders.length);
|
||||||
|
|
||||||
return sendRequest(opCode, sendHeaders, header, privateInput);
|
return sendRequest(opCode, sendHeaders, header, privateInput, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -520,4 +609,8 @@ public final class ClientSession extends ObexSession {
|
|||||||
mInput.close();
|
mInput.close();
|
||||||
mOutput.close();
|
mOutput.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSrmSupported() {
|
||||||
|
return mLocalSrmSupported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import java.security.SecureRandom;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements the javax.obex.HeaderSet interface for OBEX over
|
* This class implements the javax.obex.HeaderSet interface for OBEX over
|
||||||
* RFCOMM.
|
* RFCOMM or OBEX over l2cap.
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public final class HeaderSet {
|
public final class HeaderSet {
|
||||||
@ -178,6 +178,22 @@ public final class HeaderSet {
|
|||||||
*/
|
*/
|
||||||
public static final int OBJECT_CLASS = 0x4F;
|
public static final int OBJECT_CLASS = 0x4F;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the OBEX Single Response Mode (SRM). This header is used
|
||||||
|
* for Single response mode, introduced in OBEX 1.5.
|
||||||
|
* <P>
|
||||||
|
* The value of <code>SINGLE_RESPONSE_MODE</code> is 0x97 (151).
|
||||||
|
*/
|
||||||
|
public static final int SINGLE_RESPONSE_MODE = 0x97;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the OBEX Single Response Mode Parameters. This header is used
|
||||||
|
* for Single response mode, introduced in OBEX 1.5.
|
||||||
|
* <P>
|
||||||
|
* The value of <code>SINGLE_RESPONSE_MODE_PARAMETER</code> is 0x98 (152).
|
||||||
|
*/
|
||||||
|
public static final int SINGLE_RESPONSE_MODE_PARAMETER = 0x98;
|
||||||
|
|
||||||
private Long mCount; // 4 byte unsigned integer
|
private Long mCount; // 4 byte unsigned integer
|
||||||
|
|
||||||
private String mName; // null terminated Unicode text string
|
private String mName; // null terminated Unicode text string
|
||||||
@ -204,7 +220,7 @@ public final class HeaderSet {
|
|||||||
|
|
||||||
private byte[] mObjectClass; // byte sequence
|
private byte[] mObjectClass; // byte sequence
|
||||||
|
|
||||||
private String[] mUnicodeUserDefined; //null terminated unicode string
|
private String[] mUnicodeUserDefined; // null terminated unicode string
|
||||||
|
|
||||||
private byte[][] mSequenceUserDefined; // byte sequence user defined
|
private byte[][] mSequenceUserDefined; // byte sequence user defined
|
||||||
|
|
||||||
@ -212,7 +228,12 @@ public final class HeaderSet {
|
|||||||
|
|
||||||
private Long[] mIntegerUserDefined; // 4 byte unsigned integer
|
private Long[] mIntegerUserDefined; // 4 byte unsigned integer
|
||||||
|
|
||||||
private final SecureRandom mRandom;
|
private SecureRandom mRandom = null;
|
||||||
|
|
||||||
|
private Byte mSingleResponseMode; // byte to indicate enable/disable/support for SRM
|
||||||
|
|
||||||
|
private Byte mSrmParam; // byte representing the SRM parameters - only "wait"
|
||||||
|
// is supported by Bluetooth
|
||||||
|
|
||||||
/*package*/ byte[] nonce;
|
/*package*/ byte[] nonce;
|
||||||
|
|
||||||
@ -234,7 +255,6 @@ public final class HeaderSet {
|
|||||||
mByteUserDefined = new Byte[16];
|
mByteUserDefined = new Byte[16];
|
||||||
mIntegerUserDefined = new Long[16];
|
mIntegerUserDefined = new Long[16];
|
||||||
responseCode = -1;
|
responseCode = -1;
|
||||||
mRandom = new SecureRandom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -393,6 +413,30 @@ public final class HeaderSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SINGLE_RESPONSE_MODE:
|
||||||
|
if (headerValue == null) {
|
||||||
|
mSingleResponseMode = null;
|
||||||
|
} else {
|
||||||
|
if (!(headerValue instanceof Byte)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Single Response Mode must be a Byte");
|
||||||
|
} else {
|
||||||
|
mSingleResponseMode = (Byte)headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SINGLE_RESPONSE_MODE_PARAMETER:
|
||||||
|
if (headerValue == null) {
|
||||||
|
mSrmParam = null;
|
||||||
|
} else {
|
||||||
|
if (!(headerValue instanceof Byte)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Single Response Mode Parameter must be a Byte");
|
||||||
|
} else {
|
||||||
|
mSrmParam = (Byte)headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// Verify that it was not a Unicode String user Defined
|
// Verify that it was not a Unicode String user Defined
|
||||||
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
||||||
@ -493,6 +537,10 @@ public final class HeaderSet {
|
|||||||
return mObjectClass;
|
return mObjectClass;
|
||||||
case APPLICATION_PARAMETER:
|
case APPLICATION_PARAMETER:
|
||||||
return mAppParam;
|
return mAppParam;
|
||||||
|
case SINGLE_RESPONSE_MODE:
|
||||||
|
return mSingleResponseMode;
|
||||||
|
case SINGLE_RESPONSE_MODE_PARAMETER:
|
||||||
|
return mSrmParam;
|
||||||
default:
|
default:
|
||||||
// Verify that it was not a Unicode String user Defined
|
// Verify that it was not a Unicode String user Defined
|
||||||
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
if ((headerID >= 0x30) && (headerID <= 0x3F)) {
|
||||||
@ -564,6 +612,12 @@ public final class HeaderSet {
|
|||||||
if (mObjectClass != null) {
|
if (mObjectClass != null) {
|
||||||
out.write(OBJECT_CLASS);
|
out.write(OBJECT_CLASS);
|
||||||
}
|
}
|
||||||
|
if(mSingleResponseMode != null) {
|
||||||
|
out.write(SINGLE_RESPONSE_MODE);
|
||||||
|
}
|
||||||
|
if(mSrmParam != null) {
|
||||||
|
out.write(SINGLE_RESPONSE_MODE_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0x30; i < 0x40; i++) {
|
for (int i = 0x30; i < 0x40; i++) {
|
||||||
if (mUnicodeUserDefined[i - 0x30] != null) {
|
if (mUnicodeUserDefined[i - 0x30] != null) {
|
||||||
@ -625,6 +679,9 @@ public final class HeaderSet {
|
|||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
nonce = new byte[16];
|
nonce = new byte[16];
|
||||||
|
if(mRandom == null) {
|
||||||
|
mRandom = new SecureRandom();
|
||||||
|
}
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
nonce[i] = (byte)mRandom.nextInt();
|
nonce[i] = (byte)mRandom.nextInt();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2014 The Android Open Source Project
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
* Copyright (C) 2015 Samsung LSI
|
||||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -42,12 +43,16 @@ import java.util.Calendar;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class defines a set of helper methods for the implementation of Obex.
|
* This class defines a set of helper methods for the implementation of Obex.
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public final class ObexHelper {
|
public final class ObexHelper {
|
||||||
|
|
||||||
|
private static final String TAG = "ObexHelper";
|
||||||
|
public static final boolean VDBG = false;
|
||||||
/**
|
/**
|
||||||
* Defines the basic packet length used by OBEX. Every OBEX packet has the
|
* Defines the basic packet length used by OBEX. Every OBEX packet has the
|
||||||
* same basic format:<BR>
|
* same basic format:<BR>
|
||||||
@ -65,18 +70,24 @@ public final class ObexHelper {
|
|||||||
* should be the Max incoming MTU minus TODO: L2CAP package headers and
|
* should be the Max incoming MTU minus TODO: L2CAP package headers and
|
||||||
* RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
|
* RFCOMM package headers. TODO: Retrieve the max incoming MTU from TODO:
|
||||||
* LocalDevice.getProperty().
|
* LocalDevice.getProperty().
|
||||||
|
* NOTE: This value must be larger than or equal to the L2CAP SDU
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* android note set as 0xFFFE to match remote MPS
|
* android note set as 0xFFFE to match remote MPS
|
||||||
*/
|
*/
|
||||||
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
|
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
|
||||||
|
|
||||||
|
// The minimum allowed max packet size is 255 according to the OBEX specification
|
||||||
|
public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary workaround to be able to push files to Windows 7.
|
* Temporary workaround to be able to push files to Windows 7.
|
||||||
* TODO: Should be removed as soon as Microsoft updates their driver.
|
* TODO: Should be removed as soon as Microsoft updates their driver.
|
||||||
*/
|
*/
|
||||||
public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
|
public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
|
||||||
|
|
||||||
|
public static final int OBEX_OPCODE_FINAL_BIT_MASK = 0x80;
|
||||||
|
|
||||||
public static final int OBEX_OPCODE_CONNECT = 0x80;
|
public static final int OBEX_OPCODE_CONNECT = 0x80;
|
||||||
|
|
||||||
public static final int OBEX_OPCODE_DISCONNECT = 0x81;
|
public static final int OBEX_OPCODE_DISCONNECT = 0x81;
|
||||||
@ -119,6 +130,12 @@ public final class ObexHelper {
|
|||||||
|
|
||||||
public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
|
public static final int OBEX_AUTH_REALM_CHARSET_UNICODE = 0xFF;
|
||||||
|
|
||||||
|
public static final byte OBEX_SRM_ENABLE = 0x01; // For BT we only need enable/disable
|
||||||
|
public static final byte OBEX_SRM_DISABLE = 0x00;
|
||||||
|
public static final byte OBEX_SRM_SUPPORT = 0x02; // Unused for now
|
||||||
|
|
||||||
|
public static final byte OBEX_SRMP_WAIT = 0x01; // Only SRMP value used by BT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the HeaderSet with the headers received in the byte array
|
* Updates the HeaderSet with the headers received in the byte array
|
||||||
* provided. Invalid headers are ignored.
|
* provided. Invalid headers are ignored.
|
||||||
@ -314,7 +331,7 @@ public final class ObexHelper {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Not a valid header so ignore
|
// Not a valid header so ignore
|
||||||
throw new IOException("Header was not formatted properly");
|
throw new IOException("Header was not formatted properly", e);
|
||||||
}
|
}
|
||||||
index += 4;
|
index += 4;
|
||||||
break;
|
break;
|
||||||
@ -322,7 +339,7 @@ public final class ObexHelper {
|
|||||||
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IOException("Header was not formatted properly");
|
throw new IOException("Header was not formatted properly", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return body;
|
return body;
|
||||||
@ -672,6 +689,33 @@ public final class ObexHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// If the SRM and SRMP header is in use, they must be send in the same OBEX packet
|
||||||
|
// But the current structure of the obex code cannot handle this, and therefore
|
||||||
|
// it makes sense to put them in the tail of the headers, since we then reduce the
|
||||||
|
// chance of enabling SRM to soon. The down side is that SRM cannot be used while
|
||||||
|
// transferring non-body headers
|
||||||
|
|
||||||
|
// Add the SRM header
|
||||||
|
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||||
|
if (byteHeader != null) {
|
||||||
|
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE);
|
||||||
|
out.write(byteHeader.byteValue());
|
||||||
|
if (nullOut) {
|
||||||
|
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the SRM parameter header
|
||||||
|
byteHeader = (Byte)headImpl.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||||
|
if (byteHeader != null) {
|
||||||
|
out.write((byte)HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||||
|
out.write(byteHeader.byteValue());
|
||||||
|
if (nullOut) {
|
||||||
|
headImpl.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
} finally {
|
} finally {
|
||||||
result = out.toByteArray();
|
result = out.toByteArray();
|
||||||
@ -702,6 +746,8 @@ public final class ObexHelper {
|
|||||||
int index = start;
|
int index = start;
|
||||||
int length = 0;
|
int length = 0;
|
||||||
|
|
||||||
|
// TODO: Ensure SRM and SRMP headers are not split into two OBEX packets
|
||||||
|
|
||||||
while ((fullLength < maxSize) && (index < headerArray.length)) {
|
while ((fullLength < maxSize) && (index < headerArray.length)) {
|
||||||
int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
|
int headerID = (headerArray[index] < 0 ? headerArray[index] + 256 : headerArray[index]);
|
||||||
lastLength = fullLength;
|
lastLength = fullLength;
|
||||||
@ -1008,4 +1054,39 @@ public final class ObexHelper {
|
|||||||
|
|
||||||
return authChall;
|
return authChall;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the maximum allowed OBEX packet to transmit.
|
||||||
|
* OBEX packets transmitted must be smaller than this value.
|
||||||
|
* @param transport Reference to the ObexTransport in use.
|
||||||
|
* @return the maximum allowed OBEX packet to transmit
|
||||||
|
*/
|
||||||
|
public static int getMaxTxPacketSize(ObexTransport transport) {
|
||||||
|
int size = transport.getMaxTransmitPacketSize();
|
||||||
|
return validateMaxPacketSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the maximum allowed OBEX packet to receive - used in OBEX connect.
|
||||||
|
* @param transport
|
||||||
|
* @return he maximum allowed OBEX packet to receive
|
||||||
|
*/
|
||||||
|
public static int getMaxRxPacketSize(ObexTransport transport) {
|
||||||
|
int size = transport.getMaxReceivePacketSize();
|
||||||
|
return validateMaxPacketSize(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validateMaxPacketSize(int size) {
|
||||||
|
if(VDBG && (size > MAX_PACKET_SIZE_INT)) Log.w(TAG,
|
||||||
|
"The packet size supported for the connection (" + size + ") is larger"
|
||||||
|
+ " than the configured OBEX packet size: " + MAX_PACKET_SIZE_INT);
|
||||||
|
if(size != -1) {
|
||||||
|
if(size < LOWER_LIMIT_MAX_PACKET_SIZE) {
|
||||||
|
throw new IllegalArgumentException(size + " is less that the lower limit: "
|
||||||
|
+ LOWER_LIMIT_MAX_PACKET_SIZE);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
return MAX_PACKET_SIZE_INT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
71
obex/javax/obex/ObexPacket.java
Normal file
71
obex/javax/obex/ObexPacket.java
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2015 The Android Open Source Project
|
||||||
|
* Copyright (c) 2015 Samsung LSI
|
||||||
|
*
|
||||||
|
* 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 javax.obex;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ObexPacket {
|
||||||
|
public int mHeaderId;
|
||||||
|
public int mLength;
|
||||||
|
public byte[] mPayload = null;
|
||||||
|
|
||||||
|
private ObexPacket(int headerId, int length) {
|
||||||
|
mHeaderId = headerId;
|
||||||
|
mLength = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a complete OBEX packet by reading data from an InputStream.
|
||||||
|
* @param is the input stream to read from.
|
||||||
|
* @return the OBEX packet read.
|
||||||
|
* @throws IOException if an IO exception occurs during read.
|
||||||
|
*/
|
||||||
|
public static ObexPacket read(InputStream is) throws IOException {
|
||||||
|
int headerId = is.read();
|
||||||
|
return read(headerId, is);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the remainder of an OBEX packet, with a specified headerId.
|
||||||
|
* @param headerId the headerId already read from the stream.
|
||||||
|
* @param is the stream to read from, assuming 1 byte have already been read.
|
||||||
|
* @return the OBEX packet read.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static ObexPacket read(int headerId, InputStream is) throws IOException {
|
||||||
|
// Read the 2 byte length field from the stream
|
||||||
|
int length = is.read();
|
||||||
|
length = (length << 8) + is.read();
|
||||||
|
|
||||||
|
ObexPacket newPacket = new ObexPacket(headerId, length);
|
||||||
|
|
||||||
|
int bytesReceived;
|
||||||
|
byte[] temp = null;
|
||||||
|
if (length > 3) {
|
||||||
|
// First three bytes already read, compensating for this
|
||||||
|
temp = new byte[length - 3];
|
||||||
|
bytesReceived = is.read(temp);
|
||||||
|
while (bytesReceived != temp.length) {
|
||||||
|
bytesReceived += is.read(temp, bytesReceived, temp.length - bytesReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newPacket.mPayload = temp;
|
||||||
|
return newPacket;
|
||||||
|
}
|
||||||
|
}
|
@ -34,6 +34,8 @@ package javax.obex;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The <code>ObexSession</code> interface characterizes the term
|
* The <code>ObexSession</code> interface characterizes the term
|
||||||
* "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
|
* "OBEX Connection" as defined in the IrDA Object Exchange Protocol v1.2, which
|
||||||
@ -47,6 +49,9 @@ import java.io.IOException;
|
|||||||
*/
|
*/
|
||||||
public class ObexSession {
|
public class ObexSession {
|
||||||
|
|
||||||
|
private static final String TAG = "ObexSession";
|
||||||
|
private static final boolean V = ObexHelper.VDBG;
|
||||||
|
|
||||||
protected Authenticator mAuthenticator;
|
protected Authenticator mAuthenticator;
|
||||||
|
|
||||||
protected byte[] mChallengeDigest;
|
protected byte[] mChallengeDigest;
|
||||||
@ -125,6 +130,7 @@ public class ObexSession {
|
|||||||
result = mAuthenticator
|
result = mAuthenticator
|
||||||
.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
|
.onAuthenticationChallenge(realm, isUserIDRequired, isFullAccess);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if (V) Log.d(TAG, "Exception occured - returning false", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,4 +73,39 @@ public interface ObexTransport {
|
|||||||
|
|
||||||
DataOutputStream openDataOutputStream() throws IOException;
|
DataOutputStream openDataOutputStream() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must return the maximum allowed OBEX packet that can be sent over
|
||||||
|
* the transport. For L2CAP this will be the Max SDU reported by the
|
||||||
|
* peer device.
|
||||||
|
* The returned value will be used to set the outgoing OBEX packet
|
||||||
|
* size. Therefore this value shall not change.
|
||||||
|
* For RFCOMM or other transport types where the OBEX packets size
|
||||||
|
* is unrelated to the transport packet size, return -1;
|
||||||
|
* @return the maximum allowed OBEX packet that can be send over
|
||||||
|
* the transport. Or -1 in case of don't care.
|
||||||
|
*/
|
||||||
|
int getMaxTransmitPacketSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must return the maximum allowed OBEX packet that can be received over
|
||||||
|
* the transport. For L2CAP this will be the Max SDU configured for the
|
||||||
|
* L2CAP channel.
|
||||||
|
* The returned value will be used to validate the incoming packet size
|
||||||
|
* values.
|
||||||
|
* For RFCOMM or other transport types where the OBEX packets size
|
||||||
|
* is unrelated to the transport packet size, return -1;
|
||||||
|
* @return the maximum allowed OBEX packet that can be send over
|
||||||
|
* the transport. Or -1 in case of don't care.
|
||||||
|
*/
|
||||||
|
int getMaxReceivePacketSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shall return true if the transport in use supports SRM.
|
||||||
|
* @return
|
||||||
|
* <code>true</code> if SRM operation is supported, and is to be enabled.
|
||||||
|
* <code>false</code> if SRM operations are not supported, or should not be used.
|
||||||
|
*/
|
||||||
|
boolean isSrmSupported();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/*
|
/* Copyright (c) 2015 The Android Open Source Project
|
||||||
|
* Copyright (C) 2015 Samsung LSI
|
||||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -39,6 +40,8 @@ import java.io.OutputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class implements the Operation interface for server side connections.
|
* This class implements the Operation interface for server side connections.
|
||||||
* <P>
|
* <P>
|
||||||
@ -54,6 +57,10 @@ import java.io.ByteArrayOutputStream;
|
|||||||
*/
|
*/
|
||||||
public final class ServerOperation implements Operation, BaseStream {
|
public final class ServerOperation implements Operation, BaseStream {
|
||||||
|
|
||||||
|
private static final String TAG = "ServerOperation";
|
||||||
|
|
||||||
|
private static final boolean V = ObexHelper.VDBG; // Verbose debugging
|
||||||
|
|
||||||
public boolean isAborted;
|
public boolean isAborted;
|
||||||
|
|
||||||
public HeaderSet requestHeader;
|
public HeaderSet requestHeader;
|
||||||
@ -78,6 +85,8 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
private PrivateOutputStream mPrivateOutput;
|
private PrivateOutputStream mPrivateOutput;
|
||||||
|
|
||||||
|
private ObexTransport mTransport;
|
||||||
|
|
||||||
private boolean mPrivateOutputOpen;
|
private boolean mPrivateOutputOpen;
|
||||||
|
|
||||||
private String mExceptionString;
|
private String mExceptionString;
|
||||||
@ -89,6 +98,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
private boolean mHasBody;
|
private boolean mHasBody;
|
||||||
|
|
||||||
private boolean mSendBodyHeader = true;
|
private boolean mSendBodyHeader = true;
|
||||||
|
// Assume SRM disabled - needs to be explicit
|
||||||
|
// enabled by client
|
||||||
|
private boolean mSrmEnabled = false;
|
||||||
|
// A latch - when triggered, there is not way back ;-)
|
||||||
|
private boolean mSrmActive = false;
|
||||||
|
// Set to true when a SRM enable response have been send
|
||||||
|
private boolean mSrmResponseSent = false;
|
||||||
|
// keep waiting until final-bit is received in request
|
||||||
|
// to handle the case where the SRM enable header is in
|
||||||
|
// a different OBEX packet than the SRMP header.
|
||||||
|
private boolean mSrmWaitingForRemote = true;
|
||||||
|
// Why should we wait? - currently not exposed to apps.
|
||||||
|
private boolean mSrmLocalWait = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new ServerOperation
|
* Creates new ServerOperation
|
||||||
@ -116,12 +138,14 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
mRequestFinished = false;
|
mRequestFinished = false;
|
||||||
mPrivateOutputOpen = false;
|
mPrivateOutputOpen = false;
|
||||||
mHasBody = false;
|
mHasBody = false;
|
||||||
int bytesReceived;
|
ObexPacket packet;
|
||||||
|
mTransport = p.getTransport();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if this is a PUT request
|
* Determine if this is a PUT request
|
||||||
*/
|
*/
|
||||||
if ((request == 0x02) || (request == 0x82)) {
|
if ((request == ObexHelper.OBEX_OPCODE_PUT) ||
|
||||||
|
(request == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
||||||
/*
|
/*
|
||||||
* It is a PUT request.
|
* It is a PUT request.
|
||||||
*/
|
*/
|
||||||
@ -130,13 +154,14 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
/*
|
/*
|
||||||
* Determine if the final bit is set
|
* Determine if the final bit is set
|
||||||
*/
|
*/
|
||||||
if ((request & 0x80) == 0) {
|
if ((request & ObexHelper.OBEX_OPCODE_FINAL_BIT_MASK) == 0) {
|
||||||
finalBitSet = false;
|
finalBitSet = false;
|
||||||
} else {
|
} else {
|
||||||
finalBitSet = true;
|
finalBitSet = true;
|
||||||
mRequestFinished = true;
|
mRequestFinished = true;
|
||||||
}
|
}
|
||||||
} else if ((request == 0x03) || (request == 0x83)) {
|
} else if ((request == ObexHelper.OBEX_OPCODE_GET) ||
|
||||||
|
(request == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||||
/*
|
/*
|
||||||
* It is a GET request.
|
* It is a GET request.
|
||||||
*/
|
*/
|
||||||
@ -145,71 +170,32 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
// For Get request, final bit set is decided by server side logic
|
// For Get request, final bit set is decided by server side logic
|
||||||
finalBitSet = false;
|
finalBitSet = false;
|
||||||
|
|
||||||
if (request == 0x83) {
|
if (request == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||||
mRequestFinished = true;
|
mRequestFinished = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("ServerOperation can not handle such request");
|
throw new IOException("ServerOperation can not handle such request");
|
||||||
}
|
}
|
||||||
|
|
||||||
int length = in.read();
|
packet = ObexPacket.read(request, mInput);
|
||||||
length = (length << 8) + in.read();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if the packet length is larger than this device can receive
|
* Determine if the packet length is larger than this device can receive
|
||||||
*/
|
*/
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
||||||
throw new IOException("Packet received was too large");
|
throw new IOException("Packet received was too large. Length: "
|
||||||
|
+ packet.mLength + " maxLength: " + ObexHelper.getMaxRxPacketSize(mTransport));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if any headers were sent in the initial request
|
* Determine if any headers were sent in the initial request
|
||||||
*/
|
*/
|
||||||
if (length > 3) {
|
if (packet.mLength > 3) {
|
||||||
byte[] data = new byte[length - 3];
|
if(!handleObexPacket(packet)) {
|
||||||
bytesReceived = in.read(data);
|
|
||||||
|
|
||||||
while (bytesReceived != data.length) {
|
|
||||||
bytesReceived += in.read(data, bytesReceived, data.length - bytesReceived);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
mHasBody = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
|
||||||
mListener.setConnectionId(ObexHelper.convertToLong(requestHeader.mConnectionID));
|
|
||||||
} else {
|
|
||||||
mListener.setConnectionId(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestHeader.mAuthResp != null) {
|
|
||||||
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
|
||||||
mExceptionString = "Authentication Failed";
|
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
|
||||||
mClosed = true;
|
|
||||||
requestHeader.mAuthResp = null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
if (!mHasBody) {
|
||||||
|
|
||||||
if (requestHeader.mAuthChall != null) {
|
|
||||||
mParent.handleAuthChall(requestHeader);
|
|
||||||
// send the authResp to the client
|
|
||||||
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
|
||||||
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
|
||||||
replyHeader.mAuthResp.length);
|
|
||||||
requestHeader.mAuthResp = null;
|
|
||||||
requestHeader.mAuthChall = null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
mPrivateInput.writeBytes(body, 1);
|
|
||||||
} else {
|
|
||||||
while ((!mGetOperation) && (!finalBitSet)) {
|
while ((!mGetOperation) && (!finalBitSet)) {
|
||||||
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
|
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
|
||||||
if (mPrivateInput.available() > 0) {
|
if (mPrivateInput.available() > 0) {
|
||||||
@ -232,6 +218,100 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse headers and update member variables
|
||||||
|
* @param packet the received obex packet
|
||||||
|
* @return false for failing authentication - and a OBEX_HTTP_UNAUTHORIZED
|
||||||
|
* response have been send. Else true.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private boolean handleObexPacket(ObexPacket packet) throws IOException {
|
||||||
|
byte[] body = updateRequestHeaders(packet);
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
mHasBody = true;
|
||||||
|
}
|
||||||
|
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
||||||
|
mListener.setConnectionId(ObexHelper
|
||||||
|
.convertToLong(requestHeader.mConnectionID));
|
||||||
|
} else {
|
||||||
|
mListener.setConnectionId(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestHeader.mAuthResp != null) {
|
||||||
|
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
||||||
|
mExceptionString = "Authentication Failed";
|
||||||
|
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
||||||
|
mClosed = true;
|
||||||
|
requestHeader.mAuthResp = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
requestHeader.mAuthResp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestHeader.mAuthChall != null) {
|
||||||
|
mParent.handleAuthChall(requestHeader);
|
||||||
|
// send the auhtResp to the client
|
||||||
|
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
||||||
|
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
||||||
|
replyHeader.mAuthResp.length);
|
||||||
|
requestHeader.mAuthResp = null;
|
||||||
|
requestHeader.mAuthChall = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body != null) {
|
||||||
|
mPrivateInput.writeBytes(body, 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the request header set, and sniff on SRM headers to update local state.
|
||||||
|
* @param data the OBEX packet data
|
||||||
|
* @return any bytes in a body/end-of-body header returned by {@link ObexHelper.updateHeaderSet}
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private byte[] updateRequestHeaders(ObexPacket packet) throws IOException {
|
||||||
|
byte[] body = null;
|
||||||
|
if (packet.mPayload != null) {
|
||||||
|
body = ObexHelper.updateHeaderSet(requestHeader, packet.mPayload);
|
||||||
|
}
|
||||||
|
Byte srmMode = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE);
|
||||||
|
if(mTransport.isSrmSupported() && srmMode != null
|
||||||
|
&& srmMode == ObexHelper.OBEX_SRM_ENABLE) {
|
||||||
|
mSrmEnabled = true;
|
||||||
|
if(V) Log.d(TAG,"SRM is now ENABLED (but not active) for this operation");
|
||||||
|
}
|
||||||
|
checkForSrmWait(packet.mHeaderId);
|
||||||
|
if((!mSrmWaitingForRemote) && (mSrmEnabled)) {
|
||||||
|
if(V) Log.d(TAG,"SRM is now ACTIVE for this operation");
|
||||||
|
mSrmActive = true;
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this only when a complete request have been received.
|
||||||
|
* (This is not optimal, but the current design is not really suited to
|
||||||
|
* the way SRM is specified.)
|
||||||
|
*/
|
||||||
|
private void checkForSrmWait(int headerId){
|
||||||
|
if (mSrmEnabled && (headerId == ObexHelper.OBEX_OPCODE_GET
|
||||||
|
|| headerId == ObexHelper.OBEX_OPCODE_GET_FINAL
|
||||||
|
|| headerId == ObexHelper.OBEX_OPCODE_PUT)) {
|
||||||
|
try {
|
||||||
|
mSrmWaitingForRemote = false;
|
||||||
|
Byte srmp = (Byte)requestHeader.getHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER);
|
||||||
|
if(srmp != null && srmp == ObexHelper.OBEX_SRMP_WAIT) {
|
||||||
|
mSrmWaitingForRemote = true;
|
||||||
|
// Clear the wait header, as the absents of the header when the final bit is set
|
||||||
|
// indicates don't wait.
|
||||||
|
requestHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE_PARAMETER, null);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {if(V){Log.w(TAG,"Exception while extracting header",e);}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isValidBody() {
|
public boolean isValidBody() {
|
||||||
return mHasBody;
|
return mHasBody;
|
||||||
}
|
}
|
||||||
@ -274,17 +354,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
|
* Sends a reply to the client. If the reply is a OBEX_HTTP_CONTINUE, it
|
||||||
* will wait for a response from the client before ending.
|
* will wait for a response from the client before ending unless SRM is active.
|
||||||
* @param type the response code to send back to the client
|
* @param type the response code to send back to the client
|
||||||
* @return <code>true</code> if the final bit was not set on the reply;
|
* @return <code>true</code> if the final bit was not set on the reply;
|
||||||
* <code>false</code> if no reply was received because the operation
|
* <code>false</code> if no reply was received because the operation
|
||||||
* ended, an abort was received, or the final bit was set in the
|
* ended, an abort was received, the final bit was set in the
|
||||||
* reply
|
* reply or SRM is active.
|
||||||
* @throws IOException if an IO error occurs
|
* @throws IOException if an IO error occurs
|
||||||
*/
|
*/
|
||||||
public synchronized boolean sendReply(int type) throws IOException {
|
public synchronized boolean sendReply(int type) throws IOException {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
int bytesReceived;
|
boolean skipSend = false;
|
||||||
|
boolean skipReceive = false;
|
||||||
|
boolean srmRespSendPending = false;
|
||||||
|
|
||||||
long id = mListener.getConnectionId();
|
long id = mListener.getConnectionId();
|
||||||
if (id == -1) {
|
if (id == -1) {
|
||||||
@ -293,7 +375,19 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
|
replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] headerArray = ObexHelper.createHeader(replyHeader, true);
|
if(mSrmEnabled && !mSrmResponseSent) {
|
||||||
|
// As we are not ensured that the SRM enable is in the first OBEX packet
|
||||||
|
// We must check for each reply.
|
||||||
|
if(V)Log.v(TAG, "mSrmEnabled==true, sending SRM enable response.");
|
||||||
|
replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRM_ENABLE);
|
||||||
|
srmRespSendPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSrmEnabled && !mGetOperation && mSrmLocalWait) {
|
||||||
|
replyHeader.setHeader(HeaderSet.SINGLE_RESPONSE_MODE, (byte)ObexHelper.OBEX_SRMP_WAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] headerArray = ObexHelper.createHeader(replyHeader, true); // This clears the headers
|
||||||
int bodyLength = -1;
|
int bodyLength = -1;
|
||||||
int orginalBodyLength = -1;
|
int orginalBodyLength = -1;
|
||||||
|
|
||||||
@ -347,6 +441,28 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
finalBitSet = true;
|
finalBitSet = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(mSrmActive) {
|
||||||
|
if(!mGetOperation && type == ResponseCodes.OBEX_HTTP_CONTINUE &&
|
||||||
|
mSrmResponseSent == true) {
|
||||||
|
// we are in the middle of a SRM PUT operation, don't send a continue.
|
||||||
|
skipSend = true;
|
||||||
|
} else if(mGetOperation && mRequestFinished == false && mSrmResponseSent == true) {
|
||||||
|
// We are still receiving the get request, receive, but don't send continue.
|
||||||
|
skipSend = true;
|
||||||
|
} else if(mGetOperation && mRequestFinished == true) {
|
||||||
|
// All done receiving the GET request, send data to the client, without
|
||||||
|
// expecting a continue.
|
||||||
|
skipReceive = true;
|
||||||
|
}
|
||||||
|
if(V)Log.v(TAG, "type==" + type + " skipSend==" + skipSend
|
||||||
|
+ " skipReceive==" + skipReceive);
|
||||||
|
}
|
||||||
|
if(srmRespSendPending) {
|
||||||
|
if(V)Log.v(TAG,
|
||||||
|
"SRM Enabled (srmRespSendPending == true)- sending SRM Enable response");
|
||||||
|
mSrmResponseSent = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
|
if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
|
||||||
if (bodyLength > 0) {
|
if (bodyLength > 0) {
|
||||||
/*
|
/*
|
||||||
@ -387,7 +503,7 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
|
if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
|
||||||
if(mSendBodyHeader == true) {
|
if(mSendBodyHeader) {
|
||||||
out.write(0x49);
|
out.write(0x49);
|
||||||
orginalBodyLength = 3;
|
orginalBodyLength = 3;
|
||||||
out.write((byte)(orginalBodyLength >> 8));
|
out.write((byte)(orginalBodyLength >> 8));
|
||||||
@ -395,39 +511,35 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(skipSend == false) {
|
||||||
mResponseSize = 3;
|
mResponseSize = 3;
|
||||||
mParent.sendResponse(type, out.toByteArray());
|
mParent.sendResponse(type, out.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
if (type == ResponseCodes.OBEX_HTTP_CONTINUE) {
|
||||||
int headerID = mInput.read();
|
|
||||||
int length = mInput.read();
|
|
||||||
length = (length << 8) + mInput.read();
|
|
||||||
if ((headerID != ObexHelper.OBEX_OPCODE_PUT)
|
|
||||||
&& (headerID != ObexHelper.OBEX_OPCODE_PUT_FINAL)
|
|
||||||
&& (headerID != ObexHelper.OBEX_OPCODE_GET)
|
|
||||||
&& (headerID != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
|
||||||
|
|
||||||
if (length > 3) {
|
if(mGetOperation && skipReceive) {
|
||||||
byte[] temp = new byte[length - 3];
|
// Here we need to check for and handle abort (throw an exception).
|
||||||
// First three bytes already read, compensating for this
|
// Any other signal received should be discarded silently (only on server side)
|
||||||
bytesReceived = mInput.read(temp);
|
checkSrmRemoteAbort();
|
||||||
|
} else {
|
||||||
|
// Receive and handle data (only send reply if !skipSend)
|
||||||
|
// Read a complete OBEX Packet
|
||||||
|
ObexPacket packet = ObexPacket.read(mInput);
|
||||||
|
|
||||||
while (bytesReceived != temp.length) {
|
int headerId = packet.mHeaderId;
|
||||||
bytesReceived += mInput.read(temp, bytesReceived,
|
if ((headerId != ObexHelper.OBEX_OPCODE_PUT)
|
||||||
temp.length - bytesReceived);
|
&& (headerId != ObexHelper.OBEX_OPCODE_PUT_FINAL)
|
||||||
}
|
&& (headerId != ObexHelper.OBEX_OPCODE_GET)
|
||||||
}
|
&& (headerId != ObexHelper.OBEX_OPCODE_GET_FINAL)) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if an ABORT was sent as the reply
|
* Determine if an ABORT was sent as the reply
|
||||||
*/
|
*/
|
||||||
if (headerID == ObexHelper.OBEX_OPCODE_ABORT) {
|
if (headerId == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
|
handleRemoteAbort();
|
||||||
mClosed = true;
|
|
||||||
isAborted = true;
|
|
||||||
mExceptionString = "Abort Received";
|
|
||||||
throw new IOException("Abort Received");
|
|
||||||
} else {
|
} else {
|
||||||
|
// TODO:shall we send this if it occurs during SRM? Errata on the subject
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
|
mParent.sendResponse(ResponseCodes.OBEX_HTTP_BAD_REQUEST, null);
|
||||||
mClosed = true;
|
mClosed = true;
|
||||||
mExceptionString = "Bad Request Received";
|
mExceptionString = "Bad Request Received";
|
||||||
@ -435,16 +547,16 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
if ((headerId == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
|
||||||
finalBitSet = true;
|
finalBitSet = true;
|
||||||
} else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
} else if (headerId == ObexHelper.OBEX_OPCODE_GET_FINAL) {
|
||||||
mRequestFinished = true;
|
mRequestFinished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Determine if the packet length is larger then this device can receive
|
* Determine if the packet length is larger than the negotiated packet size
|
||||||
*/
|
*/
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (packet.mLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
mParent.sendResponse(ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE, null);
|
||||||
throw new IOException("Packet received was too large");
|
throw new IOException("Packet received was too large");
|
||||||
}
|
}
|
||||||
@ -452,50 +564,13 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
/*
|
/*
|
||||||
* Determine if any headers were sent in the initial request
|
* Determine if any headers were sent in the initial request
|
||||||
*/
|
*/
|
||||||
if (length > 3) {
|
if (packet.mLength > 3 || (mSrmEnabled && packet.mLength == 3)) {
|
||||||
byte[] data = new byte[length - 3];
|
if(handleObexPacket(packet) == false) {
|
||||||
bytesReceived = mInput.read(data);
|
|
||||||
|
|
||||||
while (bytesReceived != data.length) {
|
|
||||||
bytesReceived += mInput.read(data, bytesReceived, data.length
|
|
||||||
- bytesReceived);
|
|
||||||
}
|
|
||||||
byte[] body = ObexHelper.updateHeaderSet(requestHeader, data);
|
|
||||||
if (body != null) {
|
|
||||||
mHasBody = true;
|
|
||||||
}
|
|
||||||
if (mListener.getConnectionId() != -1 && requestHeader.mConnectionID != null) {
|
|
||||||
mListener.setConnectionId(ObexHelper
|
|
||||||
.convertToLong(requestHeader.mConnectionID));
|
|
||||||
} else {
|
|
||||||
mListener.setConnectionId(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requestHeader.mAuthResp != null) {
|
|
||||||
if (!mParent.handleAuthResp(requestHeader.mAuthResp)) {
|
|
||||||
mExceptionString = "Authentication Failed";
|
|
||||||
mParent.sendResponse(ResponseCodes.OBEX_HTTP_UNAUTHORIZED, null);
|
|
||||||
mClosed = true;
|
|
||||||
requestHeader.mAuthResp = null;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
requestHeader.mAuthResp = null;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestHeader.mAuthChall != null) {
|
|
||||||
mParent.handleAuthChall(requestHeader);
|
|
||||||
// send the auhtResp to the client
|
|
||||||
replyHeader.mAuthResp = new byte[requestHeader.mAuthResp.length];
|
|
||||||
System.arraycopy(requestHeader.mAuthResp, 0, replyHeader.mAuthResp, 0,
|
|
||||||
replyHeader.mAuthResp.length);
|
|
||||||
requestHeader.mAuthResp = null;
|
|
||||||
requestHeader.mAuthChall = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
mPrivateInput.writeBytes(body, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -503,6 +578,53 @@ public final class ServerOperation implements Operation, BaseStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will look for an abort from the peer during a SRM transfer.
|
||||||
|
* The function will not block if no data has been received from the remote device.
|
||||||
|
* If data have been received, the function will block while reading the incoming
|
||||||
|
* OBEX package.
|
||||||
|
* An Abort request will be handled, and cause an IOException("Abort Received").
|
||||||
|
* Other messages will be discarded silently as per GOEP specification.
|
||||||
|
* @throws IOException if an abort request have been received.
|
||||||
|
* TODO: I think this is an error in the specification. If we discard other messages,
|
||||||
|
* the peer device will most likely stall, as it will not receive the expected
|
||||||
|
* response for the message...
|
||||||
|
* I'm not sure how to understand "Receipt of invalid or unexpected SRM or SRMP
|
||||||
|
* header values shall be ignored by the receiving device."
|
||||||
|
* If any signal is received during an active SRM transfer it is unexpected regardless
|
||||||
|
* whether or not it contains SRM/SRMP headers...
|
||||||
|
*/
|
||||||
|
private void checkSrmRemoteAbort() throws IOException {
|
||||||
|
if(mInput.available() > 0) {
|
||||||
|
ObexPacket packet = ObexPacket.read(mInput);
|
||||||
|
/*
|
||||||
|
* Determine if an ABORT was sent as the reply
|
||||||
|
*/
|
||||||
|
if (packet.mHeaderId == ObexHelper.OBEX_OPCODE_ABORT) {
|
||||||
|
handleRemoteAbort();
|
||||||
|
} else {
|
||||||
|
// TODO: should we throw an exception here anyway? - don't see how to
|
||||||
|
// ignore SRM/SRMP headers without ignoring the complete signal
|
||||||
|
// (in this particular case).
|
||||||
|
Log.w(TAG, "Received unexpected request from client - discarding...\n"
|
||||||
|
+ " headerId: " + packet.mHeaderId + " length: " + packet.mLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRemoteAbort() throws IOException {
|
||||||
|
/* TODO: To increase the speed of the abort operation in SRM, we need
|
||||||
|
* to be able to flush the L2CAP queue for the PSM in use.
|
||||||
|
* This could be implemented by introducing a control
|
||||||
|
* message to be send over the socket, that in the abort case
|
||||||
|
* could carry a flush command. */
|
||||||
|
mParent.sendResponse(ResponseCodes.OBEX_HTTP_OK, null);
|
||||||
|
mClosed = true;
|
||||||
|
isAborted = true;
|
||||||
|
mExceptionString = "Abort Received";
|
||||||
|
throw new IOException("Abort Received");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an ABORT message to the server. By calling this method, the
|
* Sends an ABORT message to the server. By calling this method, the
|
||||||
* corresponding input and output streams will be closed along with this
|
* corresponding input and output streams will be closed along with this
|
||||||
|
@ -275,4 +275,13 @@ public class ServerRequestHandler {
|
|||||||
*/
|
*/
|
||||||
public void onClose() {
|
public void onClose() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add Single Response Mode support - e.g. if the supplied
|
||||||
|
* transport is l2cap.
|
||||||
|
* @return True if SRM is supported, else False
|
||||||
|
*/
|
||||||
|
public boolean isSrmSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
|
* Copyright (C) 2015 The Android Open Source Project
|
||||||
|
* Copyright (c) 2015 Samsung LSI
|
||||||
* Copyright (c) 2008-2009, Motorola, Inc.
|
* Copyright (c) 2008-2009, Motorola, Inc.
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
@ -45,6 +47,7 @@ import java.io.OutputStream;
|
|||||||
public final class ServerSession extends ObexSession implements Runnable {
|
public final class ServerSession extends ObexSession implements Runnable {
|
||||||
|
|
||||||
private static final String TAG = "Obex ServerSession";
|
private static final String TAG = "Obex ServerSession";
|
||||||
|
private static final boolean V = ObexHelper.VDBG;
|
||||||
|
|
||||||
private ObexTransport mTransport;
|
private ObexTransport mTransport;
|
||||||
|
|
||||||
@ -91,7 +94,9 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
|
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
while (!done && !mClosed) {
|
while (!done && !mClosed) {
|
||||||
|
if(V) Log.v(TAG, "Waiting for incoming request...");
|
||||||
int requestType = mInput.read();
|
int requestType = mInput.read();
|
||||||
|
if(V) Log.v(TAG, "Read request: " + requestType);
|
||||||
switch (requestType) {
|
switch (requestType) {
|
||||||
case ObexHelper.OBEX_OPCODE_CONNECT:
|
case ObexHelper.OBEX_OPCODE_CONNECT:
|
||||||
handleConnectRequest();
|
handleConnectRequest();
|
||||||
@ -140,9 +145,9 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
Log.d(TAG, e.toString());
|
Log.d(TAG, "Exception occured - ignoring", e);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(TAG, e.toString());
|
Log.d(TAG, "Exception occured - ignoring", e);
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
@ -163,7 +168,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
|
|
||||||
int length = mInput.read();
|
int length = mInput.read();
|
||||||
length = (length << 8) + mInput.read();
|
length = (length << 8) + mInput.read();
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||||
} else {
|
} else {
|
||||||
for (int i = 3; i < length; i++) {
|
for (int i = 3; i < length; i++) {
|
||||||
@ -215,6 +220,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
*internal error should not be sent because server has already replied with
|
*internal error should not be sent because server has already replied with
|
||||||
*OK response in "sendReply")
|
*OK response in "sendReply")
|
||||||
*/
|
*/
|
||||||
|
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||||
if (!op.isAborted) {
|
if (!op.isAborted) {
|
||||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||||
}
|
}
|
||||||
@ -243,6 +249,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
op.sendReply(response);
|
op.sendReply(response);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,7 +282,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
data[2] = (byte)totalLength;
|
data[2] = (byte)totalLength;
|
||||||
}
|
}
|
||||||
op.write(data);
|
op.write(data);
|
||||||
op.flush();
|
op.flush(); // TODO: Do we need to flush?
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -304,7 +311,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
flags = mInput.read();
|
flags = mInput.read();
|
||||||
constants = mInput.read();
|
constants = mInput.read();
|
||||||
|
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||||
totalLength = 3;
|
totalLength = 3;
|
||||||
} else {
|
} else {
|
||||||
@ -358,6 +365,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
try {
|
try {
|
||||||
code = mListener.onSetPath(request, reply, backup, create);
|
code = mListener.onSetPath(request, reply, backup, create);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -425,7 +433,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
length = mInput.read();
|
length = mInput.read();
|
||||||
length = (length << 8) + mInput.read();
|
length = (length << 8) + mInput.read();
|
||||||
|
|
||||||
if (length > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (length > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||||
totalLength = 3;
|
totalLength = 3;
|
||||||
} else {
|
} else {
|
||||||
@ -466,6 +474,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
try {
|
try {
|
||||||
mListener.onDisconnect(request, reply);
|
mListener.onDisconnect(request, reply);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||||
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
sendResponse(ResponseCodes.OBEX_HTTP_INTERNAL_ERROR, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -531,23 +540,38 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
HeaderSet reply = new HeaderSet();
|
HeaderSet reply = new HeaderSet();
|
||||||
int bytesReceived;
|
int bytesReceived;
|
||||||
|
|
||||||
|
if(V) Log.v(TAG,"handleConnectRequest()");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read in the length of the OBEX packet, OBEX version, flags, and max
|
* Read in the length of the OBEX packet, OBEX version, flags, and max
|
||||||
* packet length
|
* packet length
|
||||||
*/
|
*/
|
||||||
packetLength = mInput.read();
|
packetLength = mInput.read();
|
||||||
packetLength = (packetLength << 8) + mInput.read();
|
packetLength = (packetLength << 8) + mInput.read();
|
||||||
|
if(V) Log.v(TAG,"handleConnectRequest() - packetLength: " + packetLength);
|
||||||
|
|
||||||
version = mInput.read();
|
version = mInput.read();
|
||||||
flags = mInput.read();
|
flags = mInput.read();
|
||||||
mMaxPacketLength = mInput.read();
|
mMaxPacketLength = mInput.read();
|
||||||
mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
|
mMaxPacketLength = (mMaxPacketLength << 8) + mInput.read();
|
||||||
|
|
||||||
|
if(V) Log.v(TAG,"handleConnectRequest() - version: " + version
|
||||||
|
+ " MaxLength: " + mMaxPacketLength + " flags: " + flags);
|
||||||
|
|
||||||
// should we check it?
|
// should we check it?
|
||||||
if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if (mMaxPacketLength > ObexHelper.MAX_PACKET_SIZE_INT) {
|
||||||
mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT;
|
mMaxPacketLength = ObexHelper.MAX_PACKET_SIZE_INT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packetLength > ObexHelper.MAX_PACKET_SIZE_INT) {
|
if(mMaxPacketLength > ObexHelper.getMaxTxPacketSize(mTransport)) {
|
||||||
|
Log.w(TAG, "Requested MaxObexPacketSize " + mMaxPacketLength
|
||||||
|
+ " is larger than the max size supported by the transport: "
|
||||||
|
+ ObexHelper.getMaxTxPacketSize(mTransport)
|
||||||
|
+ " Reducing to this size.");
|
||||||
|
mMaxPacketLength = ObexHelper.getMaxTxPacketSize(mTransport);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetLength > ObexHelper.getMaxRxPacketSize(mTransport)) {
|
||||||
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
code = ResponseCodes.OBEX_HTTP_REQ_TOO_LARGE;
|
||||||
totalLength = 7;
|
totalLength = 7;
|
||||||
} else {
|
} else {
|
||||||
@ -614,7 +638,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
if(V) Log.d(TAG,"Exception occured - sending OBEX_HTTP_INTERNAL_ERROR reply",e);
|
||||||
totalLength = 7;
|
totalLength = 7;
|
||||||
head = null;
|
head = null;
|
||||||
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
code = ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||||
@ -633,13 +657,14 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
* Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
|
* Packet Length (Defined in MAX_PACKET_SIZE) Byte 7 to n: headers
|
||||||
*/
|
*/
|
||||||
byte[] sendData = new byte[totalLength];
|
byte[] sendData = new byte[totalLength];
|
||||||
|
int maxRxLength = ObexHelper.getMaxRxPacketSize(mTransport);
|
||||||
sendData[0] = (byte)code;
|
sendData[0] = (byte)code;
|
||||||
sendData[1] = length[2];
|
sendData[1] = length[2];
|
||||||
sendData[2] = length[3];
|
sendData[2] = length[3];
|
||||||
sendData[3] = (byte)0x10;
|
sendData[3] = (byte)0x10;
|
||||||
sendData[4] = (byte)0x00;
|
sendData[4] = (byte)0x00;
|
||||||
sendData[5] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT >> 8);
|
sendData[5] = (byte)(maxRxLength >> 8);
|
||||||
sendData[6] = (byte)(ObexHelper.MAX_PACKET_SIZE_INT & 0xFF);
|
sendData[6] = (byte)(maxRxLength & 0xFF);
|
||||||
|
|
||||||
if (head != null) {
|
if (head != null) {
|
||||||
System.arraycopy(head, 0, sendData, 7, head.length);
|
System.arraycopy(head, 0, sendData, 7, head.length);
|
||||||
@ -659,11 +684,16 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
mListener.onClose();
|
mListener.onClose();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
mInput.close();
|
/* Set state to closed before interrupting the thread by closing the streams */
|
||||||
mOutput.close();
|
|
||||||
mTransport.close();
|
|
||||||
mClosed = true;
|
mClosed = true;
|
||||||
|
if(mInput != null)
|
||||||
|
mInput.close();
|
||||||
|
if(mOutput != null)
|
||||||
|
mOutput.close();
|
||||||
|
if(mTransport != null)
|
||||||
|
mTransport.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
if(V) Log.d(TAG,"Exception occured during close() - ignore",e);
|
||||||
}
|
}
|
||||||
mTransport = null;
|
mTransport = null;
|
||||||
mInput = null;
|
mInput = null;
|
||||||
@ -702,4 +732,7 @@ public final class ServerSession extends ObexSession implements Runnable {
|
|||||||
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
return ResponseCodes.OBEX_HTTP_INTERNAL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObexTransport getTransport() {
|
||||||
|
return mTransport;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user