frameworks/base: Add PBAP support

- Update according to comments
- Add aidl support in frameworks for Settings to retrieve current
  PBAP transaction status.
- Add status bar support for PBAP
diff --git a/Android.mk b/Android.mk
index 8a4acbe..728ae46 100644
--- a/Android.mk
+++ b/Android.mk
@@ -91,6 +91,7 @@
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
 	core/java/android/bluetooth/IBluetoothDevice.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
+	core/java/android/bluetooth/IBluetoothPbap.aidl \
 	core/java/android/content/IContentService.aidl \
 	core/java/android/content/IIntentReceiver.aidl \
 	core/java/android/content/IIntentSender.aidl \
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
new file mode 100644
index 0000000..d6044145
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -0,0 +1,261 @@
+/*
+ * 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.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+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;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Public API for controlling the Bluetooth Pbap Service. This includes
+ * Bluetooth Phone book Access profile.
+ * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
+ * Service via IPC.
+ *
+ * Creating a BluetoothPbap object will create a binding with the
+ * BluetoothPbap service. Users of this object should call close() when they
+ * are finished with the BluetoothPbap, so that this proxy object can unbind
+ * from the service.
+ *
+ * This BluetoothPbap object is not immediately bound to the
+ * BluetoothPbap service. Use the ServiceListener interface to obtain a
+ * notification when it is bound, this is especially important if you wish to
+ * immediately call methods on BluetoothPbap after construction.
+ *
+ * Android only supports one connected Bluetooth Pce at a time.
+ *
+ * @hide
+ */
+public class BluetoothPbap {
+
+    private static final String TAG = "BluetoothPbap";
+    private static final boolean DBG = false;
+
+    /** int extra for PBAP_STATE_CHANGED_ACTION */
+    public static final String PBAP_STATE =
+        "android.bluetooth.pbap.intent.PBAP_STATE";
+    /** int extra for PBAP_STATE_CHANGED_ACTION */
+    public static final String PBAP_PREVIOUS_STATE =
+        "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE";
+
+    /** Indicates the state of an pbap connection state has changed.
+     *  This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and
+     *  BluetoothIntent.ADDRESS extras.
+     */
+    public static final String PBAP_STATE_CHANGED_ACTION =
+        "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED";
+
+    private IBluetoothPbap mService;
+    private final Context mContext;
+    private final ServiceListener mServiceListener;
+
+    /** There was an error trying to obtain the state */
+    public static final int STATE_ERROR        = -1;
+    /** No headset currently connected */
+    public static final int STATE_DISCONNECTED = 0;
+    /** Connection attempt in progress */
+    public static final int STATE_CONNECTING   = 1;
+    /** A headset 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;
+
+    public static final int PRIORITY_AUTO = 100;
+    public static final int PRIORITY_OFF = 0;
+    /**
+     * An interface for notifying Bluetooth PCE IPC clients when they have
+     * been connected to the BluetoothHeadset service.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when this proxy object has been
+         * connected to the BluetoothPbap service. Clients must wait for
+         * this callback before making IPC calls on the BluetoothPbap
+         * service.
+         */
+        public void onServiceConnected();
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the BluetoothPbap service. Clients must not
+         * make IPC calls on the BluetoothPbap service after this callback.
+         * This callback will currently only occur if the application hosting
+         * the BluetoothPbap service, but may be called more often in future.
+         */
+        public void onServiceDisconnected();
+    }
+
+    /**
+     * Create a BluetoothHeadset proxy object.
+     */
+    public BluetoothPbap(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        if (!context.bindService(new Intent(IBluetoothPbap.class.getName()), mConnection, 0)) {
+            Log.e(TAG, "Could not bind to Bluetooth Pbap Service");
+        }
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothHeadset will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    public synchronized void close() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+    }
+
+    /**
+     * Get the current state of the Bluetooth Headset service.
+     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
+     *         object is currently not connected to the Headset service.
+     */
+    public int getState() {
+        if (DBG) 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 BluetoothHeadset.STATE_ERROR;
+    }
+
+    /**
+     * Get the Bluetooth address of the current headset.
+     * @return The Bluetooth address, or null if not in connected or connecting
+     *         state, or if this proxy object is not connected to the Headset
+     *         service.
+     */
+    public String getPceAddress() {
+        if (DBG) log("getPceAddress()");
+        if (mService != null) {
+            try {
+                return mService.getPceAddress();
+            } 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 headset is connected (does not include
+     * connecting). Returns false if not connected, or if this proxy object
+     * if not currently connected to the headset service.
+     */
+    public boolean isConnected(String address) {
+        if (DBG) log("isConnected(" + address + ")");
+        if (mService != null) {
+            try {
+                return mService.isConnected(address);
+            } 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 headset. Currently this call blocks, it may soon
+     * be made asynchornous. Returns false if this proxy object is
+     * not currently connected to the Headset service.
+     */
+    public boolean disconnectPce() {
+        if (DBG) log("disconnectPce()");
+        if (mService != null) {
+            try {
+                mService.disconnectPce();
+                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 PBAP support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might support PBAP. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     * @return True if this device might support PBAP.
+     */
+    public static boolean doesClassMatchSink(int btClass) {
+        // TODO optimize the rule
+        switch (BluetoothClass.Device.getDevice(btClass)) {
+        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 = IBluetoothPbap.Stub.asInterface(service);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected();
+            }
+        }
+        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/IBluetoothPbap.aidl b/core/java/android/bluetooth/IBluetoothPbap.aidl
new file mode 100644
index 0000000..06cdb7b
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothPbap.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * System private API for Bluetooth pbap service
+ *
+ * {@hide}
+ */
+interface IBluetoothPbap {
+    int getState();
+    String getPceAddress();
+    boolean connectPce(in String address);
+    void disconnectPce();
+    boolean isConnected(in String address);
+}
diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java
index c8db95b..33e793b 100644
--- a/services/java/com/android/server/status/StatusBarPolicy.java
+++ b/services/java/com/android/server/status/StatusBarPolicy.java
@@ -22,6 +22,7 @@
 import android.bluetooth.BluetoothError;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothIntent;
+import android.bluetooth.BluetoothPbap;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -301,6 +302,7 @@
     private IconData mBluetoothData;
     private int mBluetoothHeadsetState;
     private int mBluetoothA2dpState;
+    private int mBluetoothPbapState;
     private boolean mBluetoothEnabled;
 
     // wifi
@@ -378,7 +380,8 @@
             }
             else if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION) ||
                     action.equals(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION) ||
-                    action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
+                    action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION) ||
+                    action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) {
                 updateBluetooth(intent);
             }
             else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION) ||
@@ -478,6 +481,7 @@
         }
         mBluetoothA2dpState = BluetoothA2dp.STATE_DISCONNECTED;
         mBluetoothHeadsetState = BluetoothHeadset.STATE_DISCONNECTED;
+        mBluetoothPbapState = BluetoothPbap.STATE_DISCONNECTED;
         mService.setIconVisibility(mBluetoothIcon, mBluetoothEnabled);
 
         // Gps status
@@ -527,6 +531,7 @@
         filter.addAction(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION);
         filter.addAction(BluetoothIntent.HEADSET_STATE_CHANGED_ACTION);
         filter.addAction(BluetoothA2dp.SINK_STATE_CHANGED_ACTION);
+        filter.addAction(BluetoothPbap.PBAP_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
@@ -1084,13 +1089,17 @@
         } else if (action.equals(BluetoothA2dp.SINK_STATE_CHANGED_ACTION)) {
             mBluetoothA2dpState = intent.getIntExtra(BluetoothA2dp.SINK_STATE,
                     BluetoothA2dp.STATE_DISCONNECTED);
+        } else if (action.equals(BluetoothPbap.PBAP_STATE_CHANGED_ACTION)) {
+            mBluetoothPbapState = intent.getIntExtra(BluetoothPbap.PBAP_STATE,
+                    BluetoothPbap.STATE_DISCONNECTED);
         } else {
             return;
         }
 
         if (mBluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED ||
                 mBluetoothA2dpState == BluetoothA2dp.STATE_CONNECTED ||
-                mBluetoothA2dpState == BluetoothA2dp.STATE_PLAYING) {
+                mBluetoothA2dpState == BluetoothA2dp.STATE_PLAYING ||
+                mBluetoothPbapState == BluetoothPbap.STATE_CONNECTED) {
             iconId = com.android.internal.R.drawable.stat_sys_data_bluetooth_connected;
         }