Bluetooth: Support MAP Client role on Bluedroid.

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

Change-Id: I1733a67bf5256bd7b181bd5e68e40b476994ebfd
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 64d80a0..a94bc41 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -306,6 +306,11 @@
     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 @@
      * 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 @@
             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)
diff --git a/core/java/android/bluetooth/BluetoothMasInstance.java b/core/java/android/bluetooth/BluetoothMasInstance.java
new file mode 100644
index 0000000..4459e2c
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothMasInstance.java
@@ -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;
+    }
+}
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index a45c6b8..df6037e 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -67,6 +67,7 @@
     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[]
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7db478d..450a889 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -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) -->
     <!-- =============================================================== -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a3262ed..a71ed47 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -761,6 +761,13 @@
      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. -->
     <string name="permdesc_getTasks">Allows the app to retrieve information
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index 0c65283..75278b5 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -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 @@
 
     private boolean mGetOperation;
 
+    private boolean mGetFinalFlag;
+
     private HeaderSet mRequestHeader;
 
     private HeaderSet mReplyHeader;
@@ -90,6 +93,7 @@
         mOperationDone = false;
         mMaxPacketSize = maxSize;
         mGetOperation = type;
+        mGetFinalFlag = false;
 
         mPrivateInputOpen = false;
         mPrivateOutputOpen = false;
@@ -131,6 +135,15 @@
     }
 
     /**
+     * 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
      * object.
@@ -551,15 +564,25 @@
 
         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) {
+                    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!");
+                    }
+
                     mOperationDone = true;
                 }
             }
@@ -613,7 +636,16 @@
                 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) {
diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java
index 2b3066f..51b560a 100644
--- a/obex/javax/obex/HeaderSet.java
+++ b/obex/javax/obex/HeaderSet.java
@@ -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 @@
 
     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
@@ -235,6 +238,25 @@
     }
 
     /**
+     * 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
      * this interface. If <code>null</code> is passed as the
@@ -269,6 +291,7 @@
                 if ((headerValue != null) && (!(headerValue instanceof String))) {
                     throw new IllegalArgumentException("Name must be a String");
                 }
+                mEmptyName = false;
                 mName = (String)headerValue;
                 break;
             case TYPE:
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index 8c12a20..0a06709 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -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 @@
                 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