Change the routing path of bluetooth headset connections.

The HeadsetService is now bound directly by the BluetoothManagerService.
The IBinder object related to the HeadsetService is then given back to
the BluetoothHeadset and to the client app. This change makes the
HeadsetService available for managed profile clients.

Bug: 16968338
Change-Id: I016d1837e4f987c0fab1fc2c64cb06eb91b24d87
diff --git a/Android.mk b/Android.mk
index ce72a85..51b09b9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -99,6 +99,7 @@
 	core/java/android/bluetooth/IBluetoothA2dpSink.aidl \
 	core/java/android/bluetooth/IBluetoothAvrcpController.aidl \
 	core/java/android/bluetooth/IBluetoothCallback.aidl \
+	core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl \
 	core/java/android/bluetooth/IBluetoothHeadset.aidl \
 	core/java/android/bluetooth/IBluetoothHeadsetPhone.aidl \
 	core/java/android/bluetooth/IBluetoothHealth.aidl \
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 353f0fb..546a50e 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -20,9 +20,10 @@
 import android.annotation.SdkConstant.SdkConstantType;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -221,11 +222,14 @@
      */
     public static final int STATE_AUDIO_CONNECTED = 12;
 
+    private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
+    private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
 
     private Context mContext;
     private ServiceListener mServiceListener;
     private IBluetoothHeadset mService;
     private BluetoothAdapter mAdapter;
+    private boolean mIsClosed;
 
     final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
             new IBluetoothStateChangeCallback.Stub() {
@@ -233,14 +237,7 @@
                     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);
-                            }
-                        }
+                        doUnbind();
                     } else {
                         synchronized (mConnection) {
                             try {
@@ -263,6 +260,7 @@
         mContext = context;
         mServiceListener = l;
         mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mIsClosed = false;
 
         IBluetoothManager mgr = mAdapter.getBluetoothManager();
         if (mgr != null) {
@@ -277,15 +275,26 @@
     }
 
     boolean doBind() {
-        Intent intent = new Intent(IBluetoothHeadset.class.getName());
-        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
-        intent.setComponent(comp);
-        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
-                android.os.Process.myUserHandle())) {
-            Log.e(TAG, "Could not bind to Bluetooth Headset Service with " + intent);
-            return false;
+        try {
+            return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+                    BluetoothProfile.HEADSET, mConnection);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to bind HeadsetService", e);
         }
-        return true;
+        return false;
+    }
+
+    void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                try {
+                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
+                            BluetoothProfile.HEADSET, mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG,"Unable to unbind HeadsetService", e);
+                }
+            }
+        }
     }
 
     /**
@@ -305,18 +314,8 @@
                 Log.e(TAG,"",e);
             }
         }
-
-        synchronized (mConnection) {
-            if (mService != null) {
-                try {
-                    mService = null;
-                    mContext.unbindService(mConnection);
-                } catch (Exception re) {
-                    Log.e(TAG,"",re);
-                }
-            }
-        }
-        mServiceListener = null;
+        mIsClosed = true;
+        doUnbind();
     }
 
     /**
@@ -930,21 +929,21 @@
         return false;
     }
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
+    private final IBluetoothProfileServiceConnection mConnection
+            = new IBluetoothProfileServiceConnection.Stub()  {
+        @Override
         public void onServiceConnected(ComponentName className, IBinder service) {
             if (DBG) Log.d(TAG, "Proxy object connected");
             mService = IBluetoothHeadset.Stub.asInterface(service);
-
-            if (mServiceListener != null) {
-                mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, BluetoothHeadset.this);
-            }
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_CONNECTED));
         }
+        @Override
         public void onServiceDisconnected(ComponentName className) {
             if (DBG) Log.d(TAG, "Proxy object disconnected");
             mService = null;
-            if (mServiceListener != null) {
-                mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
-            }
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
         }
     };
 
@@ -968,4 +967,28 @@
     private static void log(String msg) {
         Log.d(TAG, msg);
     }
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
+                                BluetoothHeadset.this);
+                    }
+                    break;
+                }
+                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
+                    }
+                    if (mIsClosed){
+                        mServiceListener = null;
+                    }
+                    break;
+                }
+            }
+        }
+    };
 }
diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl
index 493d2f8..7411d3f 100644
--- a/core/java/android/bluetooth/IBluetoothManager.aidl
+++ b/core/java/android/bluetooth/IBluetoothManager.aidl
@@ -19,6 +19,7 @@
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
 
 /**
@@ -38,6 +39,9 @@
     boolean disable(boolean persist);
     IBluetoothGatt getBluetoothGatt();
 
+    boolean bindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+    void unbindBluetoothProfileService(int profile, IBluetoothProfileServiceConnection proxy);
+
     String getAddress();
     String getName();
 }
diff --git a/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
new file mode 100755
index 0000000..96c59e2
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothProfileServiceConnection.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.content.ComponentName;
+import android.os.IBinder;
+
+/**
+ * Callback for bluetooth profile connections.
+ *
+ * {@hide}
+ */
+interface IBluetoothProfileServiceConnection {
+    void onServiceConnected(in ComponentName comp, in IBinder service);
+    void onServiceDisconnected(in ComponentName comp);
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index ebdd386..3117a17 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -18,11 +18,14 @@
 
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.IBluetooth;
 import android.bluetooth.IBluetoothGatt;
 import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothHeadset;
 import android.bluetooth.IBluetoothManager;
 import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothProfileServiceConnection;
 import android.bluetooth.IBluetoothStateChangeCallback;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -32,6 +35,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -42,12 +46,18 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Vector;
+
 class BluetoothManagerService extends IBluetoothManager.Stub {
     private static final String TAG = "BluetoothManagerService";
     private static final boolean DBG = true;
@@ -67,6 +77,8 @@
     private static final int ERROR_RESTART_TIME_MS = 3000;
     //Maximum msec to delay MESSAGE_USER_SWITCHED
     private static final int USER_SWITCHED_TIME_MS = 200;
+    // Delay for the addProxy function in msec
+    private static final int ADD_PROXY_DELAY_MS = 100;
 
     private static final int MESSAGE_ENABLE = 1;
     private static final int MESSAGE_DISABLE = 2;
@@ -83,6 +95,8 @@
     private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
     private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
     private static final int MESSAGE_USER_SWITCHED = 300;
+    private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
+    private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
     private static final int MAX_SAVE_RETRIES=3;
     private static final int MAX_ERROR_RESTART_RETRIES=6;
 
@@ -127,6 +141,11 @@
     private int mErrorRecoveryRetryCounter;
     private final int mSystemUiUid;
 
+    // Save a ProfileServiceConnections object for each of the bound
+    // bluetooth profile services
+    private final Map <Integer, ProfileServiceConnections> mProfileServices =
+            new HashMap <Integer, ProfileServiceConnections>();
+
     private void registerForAirplaneMode(IntentFilter filter) {
         final ContentResolver resolver = mContext.getContentResolver();
         final String airplaneModeRadios = Settings.Global.getString(resolver,
@@ -499,6 +518,187 @@
         return mBluetoothGatt;
     }
 
+    @Override
+    public boolean bindBluetoothProfileService(int bluetoothProfile,
+            IBluetoothProfileServiceConnection proxy) {
+        if (!mEnable) {
+            if (DBG) {
+                Log.d(TAG, "Trying to bind to profile: " + bluetoothProfile +
+                        ", while Bluetooth was disabled");
+            }
+            return false;
+        }
+        synchronized (mProfileServices) {
+            ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+            if (psc == null) {
+                if (DBG) {
+                    Log.d(TAG, "Creating new ProfileServiceConnections object for"
+                            + " profile: " + bluetoothProfile);
+                }
+                Intent intent = null;
+                if (bluetoothProfile == BluetoothProfile.HEADSET) {
+                    intent = new Intent(IBluetoothHeadset.class.getName());
+                } else {
+                    return false;
+                }
+                psc = new ProfileServiceConnections(intent);
+                mProfileServices.put(new Integer(bluetoothProfile), psc);
+                psc.bindService();
+            }
+        }
+
+        // Introducing a delay to give the client app time to prepare
+        Message addProxyMsg = mHandler.obtainMessage(MESSAGE_ADD_PROXY_DELAYED);
+        addProxyMsg.arg1 = bluetoothProfile;
+        addProxyMsg.obj = proxy;
+        mHandler.sendMessageDelayed(addProxyMsg, ADD_PROXY_DELAY_MS);
+        return true;
+    }
+
+    @Override
+    public void unbindBluetoothProfileService(int bluetoothProfile,
+            IBluetoothProfileServiceConnection proxy) {
+        synchronized (mProfileServices) {
+            ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
+            if (psc == null) {
+                return;
+            }
+            psc.removeProxy(proxy);
+        }
+    }
+
+    private void unbindAllBluetoothProfileServices() {
+        synchronized (mProfileServices) {
+            for (Integer i : mProfileServices.keySet()) {
+                ProfileServiceConnections psc = mProfileServices.get(i);
+                mContext.unbindService(psc);
+                psc.removeAllProxies();
+            }
+            mProfileServices.clear();
+        }
+    }
+
+    /**
+     * This class manages the clients connected to a given ProfileService
+     * and maintains the connection with that service.
+     */
+    final private class ProfileServiceConnections implements ServiceConnection,
+            IBinder.DeathRecipient {
+        final RemoteCallbackList<IBluetoothProfileServiceConnection> mProxies =
+                new RemoteCallbackList <IBluetoothProfileServiceConnection>();
+        IBinder mService;
+        ComponentName mClassName;
+        Intent mIntent;
+
+        ProfileServiceConnections(Intent intent) {
+            mService = null;
+            mClassName = null;
+            mIntent = intent;
+        }
+
+        private void bindService() {
+            if (mIntent != null && mService == null) {
+                if (!doBind(mIntent, this, 0, UserHandle.CURRENT_OR_SELF)) {
+                    Log.w(TAG, "Unable to bind with intent: " + mIntent
+                            + ". Triggering retry.");
+                }
+                Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+                msg.obj = this;
+                mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+            }
+        }
+
+        private void addProxy(IBluetoothProfileServiceConnection proxy) {
+            mProxies.register(proxy);
+            if (mService != null) {
+                try{
+                    proxy.onServiceConnected(mClassName, mService);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to connect to proxy", e);
+                }
+            } else {
+                if (!mHandler.hasMessages(MESSAGE_BIND_PROFILE_SERVICE, this)) {
+                    Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+                    msg.obj = this;
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+
+        private void removeProxy(IBluetoothProfileServiceConnection proxy) {
+            if (proxy != null) {
+                if (mProxies.unregister(proxy)) {
+                    try {
+                        proxy.onServiceDisconnected(mClassName);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Unable to disconnect proxy", e);
+                    }
+                }
+            } else {
+                Log.w(TAG, "Trying to remove a null proxy");
+            }
+        }
+
+        private void removeAllProxies() {
+            onServiceDisconnected(mClassName);
+            mProxies.kill();
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            // remove timeout message
+            mHandler.removeMessages(MESSAGE_BIND_PROFILE_SERVICE, this);
+            mService = service;
+            mClassName = className;
+            try {
+                mService.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to linkToDeath", e);
+            }
+            int n = mProxies.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                try {
+                    mProxies.getBroadcastItem(i).onServiceConnected(className, service);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to connect to proxy", e);
+                }
+            }
+            mProxies.finishBroadcast();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (mService == null) {
+                return;
+            }
+            mService.unlinkToDeath(this, 0);
+            mService = null;
+            mClassName = null;
+            int n = mProxies.beginBroadcast();
+            for (int i = 0; i < n; i++) {
+                try {
+                    mProxies.getBroadcastItem(i).onServiceDisconnected(className);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to disconnect from proxy", e);
+                }
+            }
+            mProxies.finishBroadcast();
+        }
+
+        @Override
+        public void binderDied() {
+            if (DBG) {
+                Log.w(TAG, "Profile service for profile: " + mClassName
+                        + " died.");
+            }
+            onServiceDisconnected(mClassName);
+            // Trigger rebind
+            Message msg = mHandler.obtainMessage(MESSAGE_BIND_PROFILE_SERVICE);
+            msg.obj = this;
+            mHandler.sendMessageDelayed(msg, TIMEOUT_BIND_MS);
+        }
+    }
+
     private void sendBluetoothStateCallback(boolean isUp) {
         int n = mStateChangeCallbacks.beginBroadcast();
         if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
@@ -803,6 +1003,28 @@
                     }
                     break;
                 }
+                case MESSAGE_ADD_PROXY_DELAYED:
+                {
+                    ProfileServiceConnections psc = mProfileServices.get(
+                            new Integer(msg.arg1));
+                    if (psc == null) {
+                        break;
+                    }
+                    IBluetoothProfileServiceConnection proxy =
+                            (IBluetoothProfileServiceConnection) msg.obj;
+                    psc.addProxy(proxy);
+                    break;
+                }
+                case MESSAGE_BIND_PROFILE_SERVICE:
+                {
+                    ProfileServiceConnections psc = (ProfileServiceConnections) msg.obj;
+                    removeMessages(MESSAGE_BIND_PROFILE_SERVICE, msg.obj);
+                    if (psc == null) {
+                        break;
+                    }
+                    psc.bindService();
+                    break;
+                }
                 case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
                 {
                     if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
@@ -1005,6 +1227,7 @@
                             bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
                         }
 
+                        unbindAllBluetoothProfileServices();
                         // disable
                         handleDisable();
                         // Pbap service need receive STATE_TURNING_OFF intent to close
@@ -1129,16 +1352,21 @@
         int callingUser = UserHandle.getCallingUserId();
         int callingUid = Binder.getCallingUid();
         long callingIdentity = Binder.clearCallingIdentity();
+        UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        UserInfo ui = um.getProfileParent(callingUser);
+        int parentUser = (ui != null) ? ui.id : UserHandle.USER_NULL;
         int callingAppId = UserHandle.getAppId(callingUid);
         boolean valid = false;
         try {
             foregroundUser = ActivityManager.getCurrentUser();
             valid = (callingUser == foregroundUser) ||
+                    parentUser == foregroundUser    ||
                     callingAppId == Process.NFC_UID ||
                     callingAppId == mSystemUiUid;
             if (DBG) {
                 Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid
                     + " callingUser=" + callingUser
+                    + " parentUser=" + parentUser
                     + " foregroundUser=" + foregroundUser);
             }
         } finally {
@@ -1165,6 +1393,7 @@
                 } else {
                     //If Bluetooth is off, send service down event to proxy objects, and unbind
                     if (!isUp && canUnbindBluetoothService()) {
+                        unbindAllBluetoothProfileServices();
                         sendBluetoothServiceDownCallback();
                         unbindAndFinish();
                     }