Bluetooth: Support MAP Client role on Bluedroid.

Implementation changes to support MAP client and PBAP client
role on Bluedroid stack.

Change-Id: I1733a67bf5256bd7b181bd5e68e40b476994ebfd
This commit is contained in:
Hemant Gupta
2013-08-16 14:57:55 +05:30
committed by Mike Lockwood
parent e19a4fe32f
commit 8949bfb90c
8 changed files with 213 additions and 11 deletions

View File

@@ -306,6 +306,11 @@ public final class BluetoothDevice implements Parcelable {
public static final String ACTION_UUID =
"android.bluetooth.device.action.UUID";
/** @hide */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MAS_INSTANCE =
"android.bluetooth.device.action.MAS_INSTANCE";
/**
* Broadcast Action: Indicates a failure to retrieve the name of a remote
* device.
@@ -522,14 +527,17 @@ public final class BluetoothDevice implements Parcelable {
* Prefer BR/EDR transport for GATT connections to remote dual-mode devices
* @hide
*/
public static final int TRANSPORT_BREDR = 1;
public static final int TRANSPORT_BREDR = 1;
/**
* Prefer LE transport for GATT connections to remote dual-mode devices
* @hide
*/
public static final int TRANSPORT_LE = 2;
public static final int TRANSPORT_LE = 2;
/** @hide */
public static final String EXTRA_MAS_INSTANCE =
"android.bluetooth.device.extra.MAS_INSTANCE";
/**
* Lazy initialization. Guaranteed final after first object constructed, or
@@ -995,6 +1003,18 @@ public final class BluetoothDevice implements Parcelable {
return false;
}
/** @hide */
public boolean fetchMasInstances() {
if (sService == null) {
Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances");
return false;
}
try {
return sService.fetchRemoteMasInstances(this);
} catch (RemoteException e) {Log.e(TAG, "", e);}
return false;
}
/** @hide */
public int getServiceChannel(ParcelUuid uuid) {
//TODO(BT)

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.bluetooth;
import android.os.Parcel;
import android.os.Parcelable;
/** @hide */
public final class BluetoothMasInstance implements Parcelable {
private final int mId;
private final String mName;
private final int mChannel;
private final int mMsgTypes;
public BluetoothMasInstance(int id, String name, int channel, int msgTypes) {
mId = id;
mName = name;
mChannel = channel;
mMsgTypes = msgTypes;
}
@Override
public boolean equals(Object o) {
if (o instanceof BluetoothMasInstance) {
return mId == ((BluetoothMasInstance)o).mId;
}
return false;
}
@Override
public int hashCode() {
return mId + (mChannel << 8) + (mMsgTypes << 16);
}
@Override
public String toString() {
return Integer.toString(mId) + ":" + mName + ":" + mChannel + ":" +
Integer.toHexString(mMsgTypes);
}
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<BluetoothMasInstance> CREATOR =
new Parcelable.Creator<BluetoothMasInstance>() {
public BluetoothMasInstance createFromParcel(Parcel in) {
return new BluetoothMasInstance(in.readInt(), in.readString(),
in.readInt(), in.readInt());
}
public BluetoothMasInstance[] newArray(int size) {
return new BluetoothMasInstance[size];
}
};
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mId);
out.writeString(mName);
out.writeInt(mChannel);
out.writeInt(mMsgTypes);
}
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 int getId() {
return mId;
}
public String getName() {
return mName;
}
public int getChannel() {
return mChannel;
}
public int getMsgTypes() {
return mMsgTypes;
}
public boolean msgSupported(int msg) {
return (mMsgTypes & msg) != 0;
}
}

View File

@@ -67,6 +67,7 @@ interface IBluetooth
int getRemoteClass(in BluetoothDevice device);
ParcelUuid[] getRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteUuids(in BluetoothDevice device);
boolean fetchRemoteMasInstances(in BluetoothDevice device);
boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode);
boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[]

View File

@@ -99,6 +99,7 @@
<protected-broadcast android:name="android.bluetooth.adapter.action.LOCAL_NAME_CHANGED" />
<protected-broadcast android:name="android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.UUID" />
<protected-broadcast android:name="android.bluetooth.device.action.MAS_INSTANCE" />
<protected-broadcast android:name="android.bluetooth.device.action.ALIAS_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.FOUND" />
<protected-broadcast android:name="android.bluetooth.device.action.DISAPPEARED" />
@@ -396,6 +397,15 @@
android:label="@string/permlab_receiveWapPush"
android:description="@string/permdesc_receiveWapPush" />
<!-- Allows an application to monitor incoming Bluetooth MAP messages, to record
or perform processing on them. -->
<!-- @hide -->
<permission android:name="android.permission.RECEIVE_BLUETOOTH_MAP"
android:permissionGroup="android.permission-group.MESSAGES"
android:protectionLevel="dangerous"
android:label="@string/permlab_receiveBluetoothMap"
android:description="@string/permdesc_receiveBluetoothMap" />
<!-- =============================================================== -->
<!-- Permissions for accessing social info (contacts and social) -->
<!-- =============================================================== -->

View File

@@ -760,6 +760,13 @@
WAP messages. This permission includes the ability to monitor or delete
messages sent to you without showing them to you.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_receiveBluetoothMap">receive Bluetooth messages (MAP)</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_receiveBluetoothMap">Allows the app to receive and process Bluetooth MAP
messages. This means the app could monitor or delete messages sent to your
device without showing them to you.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_getTasks">retrieve running apps</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->

View File

@@ -1,4 +1,5 @@
/*
* Copyright (c) 2014 The Android Open Source Project
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -66,6 +67,8 @@ public final class ClientOperation implements Operation, BaseStream {
private boolean mGetOperation;
private boolean mGetFinalFlag;
private HeaderSet mRequestHeader;
private HeaderSet mReplyHeader;
@@ -90,6 +93,7 @@ public final class ClientOperation implements Operation, BaseStream {
mOperationDone = false;
mMaxPacketSize = maxSize;
mGetOperation = type;
mGetFinalFlag = false;
mPrivateInputOpen = false;
mPrivateOutputOpen = false;
@@ -130,6 +134,15 @@ public final class ClientOperation implements Operation, BaseStream {
}
}
/**
* Allows to set flag which will force GET to be always sent as single packet request with
* final flag set. This is to improve compatibility with some profiles, i.e. PBAP which
* require requests to be sent this way.
*/
public void setGetFinalFlag(boolean flag) {
mGetFinalFlag = flag;
}
/**
* Sends an ABORT message to the server. By calling this method, the
* corresponding input and output streams will be closed along with this
@@ -551,15 +564,25 @@ public final class ClientOperation implements Operation, BaseStream {
if (mGetOperation) {
if (!mOperationDone) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
more = sendRequest(0x03);
}
if (!mGetFinalFlag) {
mReplyHeader.responseCode = ResponseCodes.OBEX_HTTP_CONTINUE;
while ((more) && (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE)) {
more = sendRequest(0x03);
}
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
}
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
}
} else {
more = sendRequest(0x83);
if (more) {
throw new IOException("FINAL_GET forced but data did not fit into single packet!");
}
if (mReplyHeader.responseCode == ResponseCodes.OBEX_HTTP_CONTINUE) {
mParent.sendRequest(0x83, null, mReplyHeader, mPrivateInput);
}
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
}
}
@@ -613,7 +636,16 @@ public final class ClientOperation implements Operation, BaseStream {
if (mPrivateInput == null) {
mPrivateInput = new PrivateInputStream(this);
}
sendRequest(0x03);
if (!mGetFinalFlag) {
sendRequest(0x03);
} else {
sendRequest(0x83);
if (mReplyHeader.responseCode != ResponseCodes.OBEX_HTTP_CONTINUE) {
mOperationDone = true;
}
}
return true;
} else if (mOperationDone) {

View File

@@ -1,4 +1,5 @@
/*
* Copyright (c) 2014 The Android Open Source Project
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -181,6 +182,8 @@ public final class HeaderSet {
private String mName; // null terminated Unicode text string
private boolean mEmptyName;
private String mType; // null terminated ASCII text string
private Long mLength; // 4 byte unsigend integer
@@ -234,6 +237,25 @@ public final class HeaderSet {
mRandom = new SecureRandom();
}
/**
* Sets flag for special "value" of NAME header which should be empty. This
* is not the same as NAME header with empty string in which case it will
* have length of 5 bytes. It should be 3 bytes with only header id and
* length field.
*/
public void setEmptyNameHeader() {
mName = null;
mEmptyName = true;
}
/**
* Gets flag for special "value" of NAME header which should be empty. See
* above.
*/
public boolean getEmptyNameHeader() {
return mEmptyName;
}
/**
* Sets the value of the header identifier to the value provided. The type
* of object must correspond to the Java type defined in the description of
@@ -269,6 +291,7 @@ public final class HeaderSet {
if ((headerValue != null) && (!(headerValue instanceof String))) {
throw new IllegalArgumentException("Name must be a String");
}
mEmptyName = false;
mName = (String)headerValue;
break;
case TYPE:

View File

@@ -1,4 +1,5 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* Copyright (c) 2008-2009, Motorola, Inc.
*
* All rights reserved.
@@ -387,6 +388,11 @@ public final class ObexHelper {
if (nullOut) {
headImpl.setHeader(HeaderSet.NAME, null);
}
} else if (headImpl.getEmptyNameHeader()) {
out.write((byte) HeaderSet.NAME);
lengthArray[0] = (byte) 0x00;
lengthArray[1] = (byte) 0x03;
out.write(lengthArray);
}
// Type Header