Merge "PBAP client Settings profile." into nyc-dev
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index 736e55d..eab4c6f 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -40,7 +40,6 @@
         "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
 
     private IBluetoothPbapClient mService;
-    private BluetoothDevice mDevice;
     private final Context mContext;
     private ServiceListener mServiceListener;
     private BluetoothAdapter mAdapter;
@@ -173,7 +172,6 @@
         }
         if (mService != null && isEnabled() && isValidDevice(device)) {
             try {
-                mDevice = device;
                 return mService.connect(device);
             } catch (RemoteException e) {
                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -193,13 +191,13 @@
      * @return false on error,
      *               true otherwise
      */
-    public boolean disconnect() {
+    public boolean disconnect(BluetoothDevice device) {
         if (DBG) {
-            log("disconnect(" + mDevice + ")");
+            log("disconnect(" + device + ")" + new Exception() );
         }
-        if (mService != null && isEnabled() && isValidDevice(mDevice)) {
+        if (mService != null && isEnabled() && isValidDevice(device)) {
             try {
-                mService.disconnect(mDevice);
+                mService.disconnect(device);
                 return true;
             } catch (RemoteException e) {
               Log.e(TAG, Log.getStackTraceString(new Throwable()));
@@ -328,4 +326,66 @@
        }
        return false;
     }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     *  Priority can be one of {@link #PRIORITY_ON} or
+     * {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     */
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) {
+            log("setPriority(" + device + ", " + priority + ")");
+        }
+        if (mService != null && isEnabled() &&
+            isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF &&
+                priority != BluetoothProfile.PRIORITY_ON) {
+              return false;
+            }
+            try {
+                return mService.setPriority(device, priority);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     */
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) {
+            log("getPriority(" + device + ")");
+        }
+        if (mService != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return mService.getPriority(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return PRIORITY_OFF;
+            }
+        }
+        if (mService == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return PRIORITY_OFF;
+    }
 }
diff --git a/core/java/android/bluetooth/IBluetoothPbapClient.aidl b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
index b26ea29..6d4c5a6 100644
--- a/core/java/android/bluetooth/IBluetoothPbapClient.aidl
+++ b/core/java/android/bluetooth/IBluetoothPbapClient.aidl
@@ -29,4 +29,6 @@
     List<BluetoothDevice> getConnectedDevices();
     List<BluetoothDevice> getDevicesMatchingConnectionStates(in int[] states);
     int getConnectionState(in BluetoothDevice device);
+    boolean setPriority(in BluetoothDevice device, int priority);
+    int getPriority(in BluetoothDevice device);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a78f468..ebecfdb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7679,6 +7679,9 @@
                 BLUETOOTH_MAP_PRIORITY_PREFIX = "bluetooth_map_priority_";
         /** {@hide} */
         public static final String
+                BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX = "bluetooth_pbap_client_priority_";
+        /** {@hide} */
+        public static final String
                 BLUETOOTH_SAP_PRIORITY_PREFIX = "bluetooth_sap_priority_";
 
         /**
@@ -7834,6 +7837,14 @@
         }
 
         /**
+         * Get the key that retrieves a bluetooth pbap client priority.
+         * @hide
+         */
+        public static final String getBluetoothPbapClientPriorityKey(String address) {
+            return BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT);
+        }
+
+        /**
          * Get the key that retrieves a bluetooth map priority.
          * @hide
          */
diff --git a/packages/SettingsLib/res/values/config.xml b/packages/SettingsLib/res/values/config.xml
new file mode 100755
index 0000000..299a5b7
--- /dev/null
+++ b/packages/SettingsLib/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, 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.
+*/
+-->
+<resources>
+    <!-- Configuration for automotive -->
+    <bool name="enable_pbap_pce_profile">false</bool>
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 6226b23..6052ccd 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -24,12 +24,14 @@
 import android.bluetooth.BluetoothMap;
 import android.bluetooth.BluetoothInputDevice;
 import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothPbapClient;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
 import android.content.Intent;
 import android.os.ParcelUuid;
 import android.util.Log;
+import com.android.settingslib.R;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -82,7 +84,9 @@
     private final HidProfile mHidProfile;
     private OppProfile mOppProfile;
     private final PanProfile mPanProfile;
+    private PbapClientProfile mPbapClientProfile;
     private final PbapServerProfile mPbapProfile;
+    private final boolean mUsePbapPce;
 
     /**
      * Mapping from profile name, e.g. "HEADSET" to profile object.
@@ -99,6 +103,7 @@
         mLocalAdapter = adapter;
         mDeviceManager = deviceManager;
         mEventManager = eventManager;
+        mUsePbapPce = mContext.getResources().getBoolean(R.bool.enable_pbap_pce_profile);
         // pass this reference to adapter and event manager (circular dependency)
         mLocalAdapter.setProfileManager(this);
         mEventManager.setProfileManager(this);
@@ -205,9 +210,24 @@
         } else if (mOppProfile != null) {
             Log.w(TAG, "Warning: OPP profile was previously added but the UUID is now missing.");
         }
+
+        //PBAP Client
+        if (mUsePbapPce) {
+            if (mPbapClientProfile == null) {
+                if(DEBUG) Log.d(TAG, "Adding local PBAP Client profile");
+                mPbapClientProfile = new PbapClientProfile(mContext, mLocalAdapter, mDeviceManager,
+                        this);
+                addProfile(mPbapClientProfile, PbapClientProfile.NAME,
+                        BluetoothPbapClient.ACTION_CONNECTION_STATE_CHANGED);
+            }
+        } else if (mPbapClientProfile != null) {
+            Log.w(TAG,
+                "Warning: PBAP Client profile was previously added but the UUID is now missing.");
+        }
+
         mEventManager.registerProfileIntentReceiver();
 
-        // There is no local SDP record for HID and Settings app doesn't control PBAP
+        // There is no local SDP record for HID and Settings app doesn't control PBAP Server.
     }
 
     private final Collection<ServiceListener> mServiceListeners =
@@ -351,6 +371,10 @@
         }
     }
 
+    public PbapClientProfile getPbapClientProfile() {
+        return mPbapClientProfile;
+    }
+
     public PbapServerProfile getPbapProfile(){
         return mPbapProfile;
     }
@@ -430,6 +454,12 @@
             removedProfiles.remove(mMapProfile);
             mMapProfile.setPreferred(device, true);
         }
-    }
 
+        if (mUsePbapPce) {
+            profiles.add(mPbapClientProfile);
+            removedProfiles.remove(mPbapClientProfile);
+            profiles.remove(mPbapProfile);
+            removedProfiles.add(mPbapProfile);
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
new file mode 100755
index 0000000..aa95be2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2016 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 com.android.settingslib.bluetooth;
+
+import android.bluetooth.BluetoothPbapClient;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+final class PbapClientProfile implements LocalBluetoothProfile {
+    private static final String TAG = "PbapClientProfile";
+    private static boolean V = false;
+
+    private BluetoothPbapClient mService;
+    private boolean mIsProfileReady;
+
+    private final LocalBluetoothAdapter mLocalAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+
+    static final ParcelUuid[] SRC_UUIDS = {
+        BluetoothUuid.PBAP_PSE,
+    };
+
+    static final String NAME = "PbapClient";
+    private final LocalBluetoothProfileManager mProfileManager;
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 6;
+
+    // These callbacks run on the main thread.
+    private final class PbapClientServiceListener
+            implements BluetoothProfile.ServiceListener {
+
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            if (V) {
+                Log.d(TAG,"Bluetooth service connected");
+            }
+            mService = (BluetoothPbapClient) proxy;
+            // We just bound to the service, so refresh the UI for any connected PBAP devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // we may add a new device here, but generally this should not happen
+                if (device == null) {
+                    Log.w(TAG, "PbapClientProfile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
+                }
+                device.onProfileStateChanged(PbapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+            mIsProfileReady = true;
+        }
+
+        public void onServiceDisconnected(int profile) {
+            if (V) {
+                Log.d(TAG,"Bluetooth service disconnected");
+            }
+            mIsProfileReady = false;
+        }
+    }
+
+    private void refreshProfiles() {
+        Collection<CachedBluetoothDevice> cachedDevices = mDeviceManager.getCachedDevicesCopy();
+        for (CachedBluetoothDevice device : cachedDevices) {
+            device.onUuidChanged();
+        }
+    }
+
+    public boolean pbapClientExists() {
+        return (mService != null);
+    }
+
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    PbapClientProfile(Context context, LocalBluetoothAdapter adapter,
+            CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mLocalAdapter = adapter;
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        mLocalAdapter.getProfileProxy(context, new PbapClientServiceListener(),
+                BluetoothProfile.PBAP_CLIENT);
+    }
+
+    public boolean isConnectable() {
+        return true;
+    }
+
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (mService == null) {
+            return new ArrayList<BluetoothDevice>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(
+              new int[] {BluetoothProfile.STATE_CONNECTED,
+                         BluetoothProfile.STATE_CONNECTING,
+                         BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    public boolean connect(BluetoothDevice device) {
+        if (V) {
+            Log.d(TAG,"PBAPClientProfile got connect request");
+        }
+        if (mService == null) {
+            return false;
+        }
+        List<BluetoothDevice> srcs = getConnectedDevices();
+        if (srcs != null) {
+            for (BluetoothDevice src : srcs) {
+                if (src.equals(device)) {
+                    // Connect to same device, Ignore it
+                    Log.d(TAG,"Ignoring Connect");
+                    return true;
+                }
+            }
+            mService.disconnect(device);
+        }
+        Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
+
+        return mService.connect(device);
+    }
+
+    public boolean disconnect(BluetoothDevice device) {
+        if (V) {
+            Log.d(TAG,"PBAPClientProfile got disconnect request");
+        }
+        if (mService == null) {
+            return false;
+        }
+        return mService.disconnect(device);
+    }
+
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    public boolean isPreferred(BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+    }
+
+    public int getPreferred(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.PRIORITY_OFF;
+        }
+        return mService.getPriority(device);
+    }
+
+    public void setPreferred(BluetoothDevice device, boolean preferred) {
+        if (mService == null) {
+            return;
+        }
+        if (preferred) {
+            if (mService.getPriority(device) < BluetoothProfile.PRIORITY_ON) {
+                mService.setPriority(device, BluetoothProfile.PRIORITY_ON);
+            }
+        } else {
+            mService.setPriority(device, BluetoothProfile.PRIORITY_OFF);
+        }
+    }
+
+    public String toString() {
+        return NAME;
+    }
+
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    public int getNameResource(BluetoothDevice device) {
+        // we need to have same string in UI as the server side.
+        return R.string.bluetooth_profile_pbap;
+    }
+
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        return R.string.bluetooth_profile_pbap_summary;
+    }
+
+    public int getDrawableResource(BluetoothClass btClass) {
+        return R.drawable.ic_bt_cellphone;
+    }
+
+    protected void finalize() {
+        if (V) {
+            Log.d(TAG, "finalize()");
+        }
+        if (mService != null) {
+            try {
+                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
+                    BluetoothProfile.PBAP_CLIENT,mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up PBAP Client proxy", t);
+            }
+        }
+    }
+}