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

bug:10116530

Change-Id: I57d022005bcff5bc3e56438a81ac92566f957744
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);
+    }
+}