Bluetooth MAP profile - sms and mms support initial check-in

bug:10116530

Change-Id: I57d022005bcff5bc3e56438a81ac92566f957744
diff --git a/Android.mk b/Android.mk
index df10876..921566f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -95,6 +95,7 @@
 	core/java/android/bluetooth/IBluetoothManager.aidl \
 	core/java/android/bluetooth/IBluetoothManagerCallback.aidl \
 	core/java/android/bluetooth/IBluetoothPbap.aidl \
+	core/java/android/bluetooth/IBluetoothMap.aidl \
 	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
 	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 3ee7142..3acd9b0 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -343,6 +343,9 @@
     /**@hide*/
     public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2;
 
+    /**@hide*/
+    public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3;
+
     /**
      * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
      * Contains package name to return reply intent to.
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
new file mode 100644
index 0000000..7de309f
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2008 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.ServiceManager;
+import android.util.Log;
+
+/**
+ * This class provides the APIs to control the Bluetooth MAP
+ * Profile.
+ *@hide
+ */
+public class BluetoothMap {
+
+    private static final String TAG = "BluetoothMap";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /** int extra for MAP_STATE_CHANGED_ACTION */
+    public static final String MAP_STATE =
+        "android.bluetooth.map.intent.MAP_STATE";
+    /** int extra for MAP_STATE_CHANGED_ACTION */
+    public static final String MAP_PREVIOUS_STATE =
+        "android.bluetooth.map.intent.MAP_PREVIOUS_STATE";
+
+    /** Indicates the state of a Map connection state has changed.
+     *  This intent will always contain MAP_STATE, MAP_PREVIOUS_STATE and
+     *  BluetoothIntent.ADDRESS extras.
+     */
+    public static final String MAP_STATE_CHANGED_ACTION =
+        "android.bluetooth.map.intent.action.MAP_STATE_CHANGED";
+
+    private IBluetoothMap mService;
+    private final Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+
+    /** There was an error trying to obtain the state */
+    public static final int STATE_ERROR        = -1;
+    /** No client currently connected */
+    public static final int STATE_DISCONNECTED = 0;
+    /** Connection attempt in progress */
+    public static final int STATE_CONNECTING   = 1;
+    /** Client is currently connected */
+    public static final int STATE_CONNECTED    = 2;
+
+    public static final int RESULT_FAILURE = 0;
+    public static final int RESULT_SUCCESS = 1;
+    /** Connection canceled before completion. */
+    public static final int RESULT_CANCELED = 2;
+
+    /**
+     * An interface for notifying Bluetooth PCE IPC clients when they have
+     * been connected to the BluetoothMap service.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when this proxy object has been
+         * connected to the BluetoothMap service. Clients must wait for
+         * this callback before making IPC calls on the BluetoothMap
+         * service.
+         */
+        public void onServiceConnected(BluetoothMap proxy);
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the BluetoothMap service. Clients must not
+         * make IPC calls on the BluetoothMap service after this callback.
+         * This callback will currently only occur if the application hosting
+         * the BluetoothMap service, but may be called more often in future.
+         */
+        public void onServiceDisconnected();
+    }
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        if (VDBG) Log.d(TAG,"Unbinding service...");
+                        synchronized (mConnection) {
+                            try {
+                                mService = null;
+                                mContext.unbindService(mConnection);
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    } else {
+                        synchronized (mConnection) {
+                            try {
+                                if (mService == null) {
+                                    if (VDBG) Log.d(TAG,"Binding service...");
+                                    if (!mContext.bindService(
+                                                        new Intent(IBluetoothMap.class.getName()),
+                                                        mConnection, 0)) {
+                                        Log.e(TAG, "Could not bind to Bluetooth MAP Service");
+                                    }
+                                }
+                            } catch (Exception re) {
+                                Log.e(TAG,"",re);
+                            }
+                        }
+                    }
+                }
+        };
+
+    /**
+     * Create a BluetoothMap proxy object.
+     */
+    public BluetoothMap(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG,"",e);
+            }
+        }
+        if (!context.bindService(new Intent(IBluetoothMap.class.getName()), mConnection, 0)) {
+            Log.e(TAG, "Could not bind to Bluetooth Map Service");
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothMap will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    public synchronized void close() {
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (Exception e) {
+                Log.e(TAG,"",e);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mService = null;
+                    mContext.unbindService(mConnection);
+                    mConnection = null;
+                } catch (Exception re) {
+                    Log.e(TAG,"",re);
+                }
+            }
+        }
+        mServiceListener = null;
+    }
+
+    /**
+     * Get the current state of the BluetoothMap service.
+     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
+     *         object is currently not connected to the Map service.
+     */
+    public int getState() {
+        if (VDBG) log("getState()");
+        if (mService != null) {
+            try {
+                return mService.getState();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothMap.STATE_ERROR;
+    }
+
+    /**
+     * Get the currently connected remote Bluetooth device (PCE).
+     * @return The remote Bluetooth device, or null if not in connected or
+     *         connecting state, or if this proxy object is not connected to
+     *         the Map service.
+     */
+    public BluetoothDevice getClient() {
+        if (VDBG) log("getClient()");
+        if (mService != null) {
+            try {
+                return mService.getClient();
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the specified Bluetooth device is connected (does not
+     * include connecting). Returns false if not connected, or if this proxy
+     * object is not currently connected to the Map service.
+     */
+    public boolean isConnected(BluetoothDevice device) {
+        if (VDBG) log("isConnected(" + device + ")");
+        if (mService != null) {
+            try {
+                return mService.isConnected(device);
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Disconnects the current Map Client. Currently this call blocks,
+     * it may soon be made asynchronous. Returns false if this proxy object is
+     * not currently connected to the Map service.
+     */
+    public boolean disconnect() {
+        if (DBG) log("disconnect()");
+        if (mService != null) {
+            try {
+                mService.disconnect();
+                return true;
+            } catch (RemoteException e) {Log.e(TAG, e.toString());}
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Check class bits for possible Map support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might support Map. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     * @return True if this device might support Map.
+     */
+    public static boolean doesClassMatchSink(BluetoothClass btClass) {
+        // TODO optimize the rule
+        switch (btClass.getDeviceClass()) {
+        case BluetoothClass.Device.COMPUTER_DESKTOP:
+        case BluetoothClass.Device.COMPUTER_LAPTOP:
+        case BluetoothClass.Device.COMPUTER_SERVER:
+        case BluetoothClass.Device.COMPUTER_UNCATEGORIZED:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) log("Proxy object connected");
+            mService = IBluetoothMap.Stub.asInterface(service);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothMap.this);
+            }
+        }
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) log("Proxy object disconnected");
+            mService = null;
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected();
+            }
+        }
+    };
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 43079f4..1574090 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -98,6 +98,12 @@
     static public final int GATT_SERVER = 8;
 
     /**
+     * MAP Profile
+     * @hide
+     */
+    public static final int MAP = 9;
+
+    /**
      * Default priority for devices that we try to auto-connect to and
      * and allow incoming connections for the profile
      * @hide
diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java
index 5962235..fe66fbd 100644
--- a/core/java/android/bluetooth/BluetoothUuid.java
+++ b/core/java/android/bluetooth/BluetoothUuid.java
@@ -64,10 +64,14 @@
             ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
     public static final ParcelUuid PBAP_PSE =
             ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid MAP =
+            ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
+    public static final ParcelUuid MNS =
+            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
 
     public static final ParcelUuid[] RESERVED_UUIDS = {
         AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget,
-        ObexObjectPush, PANU, NAP};
+        ObexObjectPush, PANU, NAP, MAP, MNS};
 
     public static boolean isAudioSource(ParcelUuid uuid) {
         return uuid.equals(AudioSource);
@@ -112,6 +116,13 @@
     public static boolean isBnep(ParcelUuid uuid) {
         return uuid.equals(BNEP);
     }
+    public static boolean isMap(ParcelUuid uuid) {
+        return uuid.equals(MAP);
+    }
+    public static boolean isMns(ParcelUuid uuid) {
+        return uuid.equals(MNS);
+    }
+
     /**
      * Returns true if ParcelUuid is present in uuidArray
      *
diff --git a/core/java/android/bluetooth/IBluetoothMap.aidl b/core/java/android/bluetooth/IBluetoothMap.aidl
new file mode 100644
index 0000000..0c18e06
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothMap.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 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.bluetooth.BluetoothDevice;
+
+/**
+ * System private API for Bluetooth MAP service
+ *
+ * {@hide}
+ */
+interface IBluetoothMap {
+    int getState();
+    BluetoothDevice getClient();
+    boolean connect(in BluetoothDevice device);
+    void disconnect();
+    boolean isConnected(in BluetoothDevice device);
+}
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index 05b498c..294b502 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -723,4 +723,7 @@
             }
         }
     }
+
+    public void noBodyHeader(){
+    }
 }
diff --git a/obex/javax/obex/HeaderSet.java b/obex/javax/obex/HeaderSet.java
index b89b707..2b3066f 100644
--- a/obex/javax/obex/HeaderSet.java
+++ b/obex/javax/obex/HeaderSet.java
@@ -464,6 +464,8 @@
                 return mHttpHeader;
             case WHO:
                 return mWho;
+            case CONNECTION_ID:
+                return mConnectionID;
             case OBJECT_CLASS:
                 return mObjectClass;
             case APPLICATION_PARAMETER:
diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java
index 25656ed..5b4d5ace 100644
--- a/obex/javax/obex/Operation.java
+++ b/obex/javax/obex/Operation.java
@@ -178,4 +178,6 @@
     void close() throws IOException;
 
     int getMaxPacketSize();
+
+    public void noBodyHeader();
 }
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index d1476d2..fc441e0 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -88,6 +88,8 @@
 
     private boolean mHasBody;
 
+    private boolean mSendBodyHeader = true;
+
     /**
      * Creates new ServerOperation
      * @param p the parent that created this object
@@ -364,24 +366,33 @@
                  * (End of Body) otherwise, we need to send 0x48 (Body)
                  */
                 if ((finalBitSet) || (mPrivateOutput.isClosed())) {
-                    out.write(0x49);
+                    if(mSendBodyHeader == true) {
+                        out.write(0x49);
+                        bodyLength += 3;
+                        out.write((byte)(bodyLength >> 8));
+                        out.write((byte)bodyLength);
+                        out.write(body);
+                    }
                 } else {
+                    if(mSendBodyHeader == true) {
                     out.write(0x48);
+                    bodyLength += 3;
+                    out.write((byte)(bodyLength >> 8));
+                    out.write((byte)bodyLength);
+                    out.write(body);
+                    }
                 }
 
-                bodyLength += 3;
-                out.write((byte)(bodyLength >> 8));
-                out.write((byte)bodyLength);
-                out.write(body);
             }
         }
 
         if ((finalBitSet) && (type == ResponseCodes.OBEX_HTTP_OK) && (orginalBodyLength <= 0)) {
-            out.write(0x49);
-            orginalBodyLength = 3;
-            out.write((byte)(orginalBodyLength >> 8));
-            out.write((byte)orginalBodyLength);
-
+            if(mSendBodyHeader == true) {
+                out.write(0x49);
+                orginalBodyLength = 3;
+                out.write((byte)(orginalBodyLength >> 8));
+                out.write((byte)orginalBodyLength);
+            }
         }
 
         mResponseSize = 3;
@@ -711,4 +722,8 @@
     public void streamClosed(boolean inStream) throws IOException {
 
     }
+
+    public void noBodyHeader(){
+        mSendBodyHeader = false;
+    }
 }