Loading framework/java/android/bluetooth/BluetoothProfileConnector.java +45 −27 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; import java.util.Objects; /** * Connector for Bluetooth profile proxies to bind manager service and * profile services Loading @@ -41,9 +43,10 @@ public abstract class BluetoothProfileConnector<T> { private final int mProfileId; private BluetoothProfile.ServiceListener mServiceListener; private final BluetoothProfile mProfileProxy; private Context mContext; private String mPackageName; private final String mProfileName; private final String mServiceName; private final IBluetoothManager mBluetoothManager; private volatile T mService; private static final int MESSAGE_SERVICE_CONNECTED = 100; Loading Loading @@ -79,12 +82,28 @@ public abstract class BluetoothProfileConnector<T> { } }; BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName, String serviceName) { /** @hide */ public BluetoothProfileConnector( BluetoothProfile profile, int profileId, String profileName, String serviceName, IBluetoothManager bluetoothManager) { mProfileId = profileId; mProfileProxy = profile; mProfileName = profileName; mServiceName = serviceName; mBluetoothManager = Objects.requireNonNull(bluetoothManager); } BluetoothProfileConnector( BluetoothProfile profile, int profileId, String profileName, String serviceName) { this( profile, profileId, profileName, serviceName, BluetoothAdapter.getDefaultAdapter().getBluetoothManager()); } /** {@hide} */ Loading @@ -97,11 +116,11 @@ public abstract class BluetoothProfileConnector<T> { private boolean doBind() { synchronized (mConnection) { if (mService == null) { logDebug("Binding service for " + mContext.getPackageName()); logDebug("Binding service for " + mPackageName); mCloseGuard.open("doUnbind"); try { return BluetoothAdapter.getDefaultAdapter().getBluetoothManager() .bindBluetoothProfileService(mProfileId, mServiceName, mConnection); return mBluetoothManager.bindBluetoothProfileService( mProfileId, mServiceName, mConnection); } catch (RemoteException re) { logError("Failed to bind service. " + re); return false; Loading @@ -114,11 +133,10 @@ public abstract class BluetoothProfileConnector<T> { private void doUnbind() { synchronized (mConnection) { if (mService != null) { logDebug("Unbinding service for " + mContext.getPackageName()); logDebug("Unbinding service for " + mPackageName); mCloseGuard.close(); try { BluetoothAdapter.getDefaultAdapter().getBluetoothManager() .unbindBluetoothProfileService(mProfileId, mConnection); mBluetoothManager.unbindBluetoothProfileService(mProfileId, mConnection); } catch (RemoteException re) { logError("Unable to unbind service: " + re); } finally { Loading @@ -129,10 +147,6 @@ public abstract class BluetoothProfileConnector<T> { } void connect(Context context, BluetoothProfile.ServiceListener listener) { mContext = context; mServiceListener = listener; IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); // Preserve legacy compatibility where apps were depending on // registerStateChangeCallback() performing a permissions check which // has been relaxed in modern platform versions Loading @@ -142,30 +156,34 @@ public abstract class BluetoothProfileConnector<T> { throw new SecurityException("Need BLUETOOTH permission"); } if (mgr != null) { connect(context.getPackageName(), listener); } /** @hide */ public void connect(String packageName, BluetoothProfile.ServiceListener listener) { mPackageName = packageName; mServiceListener = listener; try { mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); mBluetoothManager.registerStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { logError("Failed to register state change callback. " + re); } } } void disconnect() { /** @hide */ public void disconnect() { if (mServiceListener != null) { BluetoothProfile.ServiceListener listener = mServiceListener; mServiceListener = null; listener.onServiceDisconnected(mProfileId); } IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); if (mgr != null) { try { mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); mBluetoothManager.unregisterStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { logError("Failed to unregister state change callback" + re); } } } T getService() { return mService; Loading framework/tests/unit/src/android/bluetooth/BluetoothProfileConnectorTest.java 0 → 100644 +168 −0 Original line number Diff line number Diff line /* * Copyright 2023 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 static com.google.common.truth.Truth.assertThat; import android.content.ComponentName; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; /** Test cases for {@link BluetoothProfileConnector}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class BluetoothProfileConnectorTest { static class FakeBluetoothManager extends IBluetoothManager.Default { private IBluetoothStateChangeCallback mStateChangeCallback; private IBluetoothProfileServiceConnection mServiceConnection; @Override public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { mStateChangeCallback = callback; } @Override public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) throws RemoteException { if (callback != mStateChangeCallback) throw new IllegalStateException(); mStateChangeCallback.onBluetoothStateChange(false); mStateChangeCallback = null; } @Override public boolean bindBluetoothProfileService( int profile, String serviceName, IBluetoothProfileServiceConnection proxy) { mServiceConnection = proxy; return true; } @Override public void unbindBluetoothProfileService( int profile, IBluetoothProfileServiceConnection proxy) { if (proxy != mServiceConnection) throw new IllegalStateException(); mServiceConnection = null; } } private BluetoothProfileConnector createBluetoothProfileConnector( IBluetoothManager bluetoothManager) { return new BluetoothProfileConnector( null, BluetoothProfile.HEADSET, "Headset", "HeadsetService", bluetoothManager) { public IBinder getServiceInterface(IBinder service) { return service; } }; } @Test public void bind_registerServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); assertThat(bluetoothManager.mServiceConnection).isNotNull(); } @Test public void unbind_unregisterServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); ComponentName componentName = new ComponentName("pkg", "cls"); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder()); bluetoothManager.mServiceConnection.onServiceDisconnected(componentName); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); assertThat(bluetoothManager.mServiceConnection).isNull(); } @Test public void upThenDown_unregisterServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); // TODO(b/302092694): Should be isNull assertThat(bluetoothManager.mServiceConnection).isNotNull(); } @Test public void disconnectAfterConnect_unregisterCallbacks() { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); connector.disconnect(); assertThat(bluetoothManager.mServiceConnection).isNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } @Test public void disconnectAfterBind_unregisterCallbacks() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); connector.disconnect(); // TODO(b/302092694): Should be isNull assertThat(bluetoothManager.mServiceConnection).isNotNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } @Test public void disconnectAfterUnbind_unregisterCallbacks() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); ComponentName componentName = new ComponentName("pkg", "cls"); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder()); bluetoothManager.mServiceConnection.onServiceDisconnected(componentName); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); connector.disconnect(); assertThat(bluetoothManager.mServiceConnection).isNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } } service/src/com/android/server/bluetooth/BluetoothManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -2037,7 +2037,9 @@ class BluetoothManagerService { IBluetoothStateChangeCallback unregCallback = (IBluetoothStateChangeCallback)msg.obj; try { // LINT.IfChange unregCallback.onBluetoothStateChange(false); // LINT.ThenChange(/framework/tests/unit/src/android/bluetooth/BluetoothProfileConnectorTest.java) } catch (RemoteException e) { Log.e(TAG, "UNREGISTER_STATE_CHANGE_CALLBACK: callback failed", e); } Loading Loading
framework/java/android/bluetooth/BluetoothProfileConnector.java +45 −27 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ import android.os.RemoteException; import android.util.CloseGuard; import android.util.Log; import java.util.Objects; /** * Connector for Bluetooth profile proxies to bind manager service and * profile services Loading @@ -41,9 +43,10 @@ public abstract class BluetoothProfileConnector<T> { private final int mProfileId; private BluetoothProfile.ServiceListener mServiceListener; private final BluetoothProfile mProfileProxy; private Context mContext; private String mPackageName; private final String mProfileName; private final String mServiceName; private final IBluetoothManager mBluetoothManager; private volatile T mService; private static final int MESSAGE_SERVICE_CONNECTED = 100; Loading Loading @@ -79,12 +82,28 @@ public abstract class BluetoothProfileConnector<T> { } }; BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName, String serviceName) { /** @hide */ public BluetoothProfileConnector( BluetoothProfile profile, int profileId, String profileName, String serviceName, IBluetoothManager bluetoothManager) { mProfileId = profileId; mProfileProxy = profile; mProfileName = profileName; mServiceName = serviceName; mBluetoothManager = Objects.requireNonNull(bluetoothManager); } BluetoothProfileConnector( BluetoothProfile profile, int profileId, String profileName, String serviceName) { this( profile, profileId, profileName, serviceName, BluetoothAdapter.getDefaultAdapter().getBluetoothManager()); } /** {@hide} */ Loading @@ -97,11 +116,11 @@ public abstract class BluetoothProfileConnector<T> { private boolean doBind() { synchronized (mConnection) { if (mService == null) { logDebug("Binding service for " + mContext.getPackageName()); logDebug("Binding service for " + mPackageName); mCloseGuard.open("doUnbind"); try { return BluetoothAdapter.getDefaultAdapter().getBluetoothManager() .bindBluetoothProfileService(mProfileId, mServiceName, mConnection); return mBluetoothManager.bindBluetoothProfileService( mProfileId, mServiceName, mConnection); } catch (RemoteException re) { logError("Failed to bind service. " + re); return false; Loading @@ -114,11 +133,10 @@ public abstract class BluetoothProfileConnector<T> { private void doUnbind() { synchronized (mConnection) { if (mService != null) { logDebug("Unbinding service for " + mContext.getPackageName()); logDebug("Unbinding service for " + mPackageName); mCloseGuard.close(); try { BluetoothAdapter.getDefaultAdapter().getBluetoothManager() .unbindBluetoothProfileService(mProfileId, mConnection); mBluetoothManager.unbindBluetoothProfileService(mProfileId, mConnection); } catch (RemoteException re) { logError("Unable to unbind service: " + re); } finally { Loading @@ -129,10 +147,6 @@ public abstract class BluetoothProfileConnector<T> { } void connect(Context context, BluetoothProfile.ServiceListener listener) { mContext = context; mServiceListener = listener; IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); // Preserve legacy compatibility where apps were depending on // registerStateChangeCallback() performing a permissions check which // has been relaxed in modern platform versions Loading @@ -142,30 +156,34 @@ public abstract class BluetoothProfileConnector<T> { throw new SecurityException("Need BLUETOOTH permission"); } if (mgr != null) { connect(context.getPackageName(), listener); } /** @hide */ public void connect(String packageName, BluetoothProfile.ServiceListener listener) { mPackageName = packageName; mServiceListener = listener; try { mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); mBluetoothManager.registerStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { logError("Failed to register state change callback. " + re); } } } void disconnect() { /** @hide */ public void disconnect() { if (mServiceListener != null) { BluetoothProfile.ServiceListener listener = mServiceListener; mServiceListener = null; listener.onServiceDisconnected(mProfileId); } IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); if (mgr != null) { try { mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); mBluetoothManager.unregisterStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { logError("Failed to unregister state change callback" + re); } } } T getService() { return mService; Loading
framework/tests/unit/src/android/bluetooth/BluetoothProfileConnectorTest.java 0 → 100644 +168 −0 Original line number Diff line number Diff line /* * Copyright 2023 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 static com.google.common.truth.Truth.assertThat; import android.content.ComponentName; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; /** Test cases for {@link BluetoothProfileConnector}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class BluetoothProfileConnectorTest { static class FakeBluetoothManager extends IBluetoothManager.Default { private IBluetoothStateChangeCallback mStateChangeCallback; private IBluetoothProfileServiceConnection mServiceConnection; @Override public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { mStateChangeCallback = callback; } @Override public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) throws RemoteException { if (callback != mStateChangeCallback) throw new IllegalStateException(); mStateChangeCallback.onBluetoothStateChange(false); mStateChangeCallback = null; } @Override public boolean bindBluetoothProfileService( int profile, String serviceName, IBluetoothProfileServiceConnection proxy) { mServiceConnection = proxy; return true; } @Override public void unbindBluetoothProfileService( int profile, IBluetoothProfileServiceConnection proxy) { if (proxy != mServiceConnection) throw new IllegalStateException(); mServiceConnection = null; } } private BluetoothProfileConnector createBluetoothProfileConnector( IBluetoothManager bluetoothManager) { return new BluetoothProfileConnector( null, BluetoothProfile.HEADSET, "Headset", "HeadsetService", bluetoothManager) { public IBinder getServiceInterface(IBinder service) { return service; } }; } @Test public void bind_registerServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); assertThat(bluetoothManager.mServiceConnection).isNotNull(); } @Test public void unbind_unregisterServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); ComponentName componentName = new ComponentName("pkg", "cls"); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder()); bluetoothManager.mServiceConnection.onServiceDisconnected(componentName); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); assertThat(bluetoothManager.mServiceConnection).isNull(); } @Test public void upThenDown_unregisterServiceConnection() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); // TODO(b/302092694): Should be isNull assertThat(bluetoothManager.mServiceConnection).isNotNull(); } @Test public void disconnectAfterConnect_unregisterCallbacks() { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); connector.disconnect(); assertThat(bluetoothManager.mServiceConnection).isNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } @Test public void disconnectAfterBind_unregisterCallbacks() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); connector.disconnect(); // TODO(b/302092694): Should be isNull assertThat(bluetoothManager.mServiceConnection).isNotNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } @Test public void disconnectAfterUnbind_unregisterCallbacks() throws RemoteException { FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(); BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager); ComponentName componentName = new ComponentName("pkg", "cls"); BluetoothProfile.ServiceListener listener = null; connector.connect("test.package", listener); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true); bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder()); bluetoothManager.mServiceConnection.onServiceDisconnected(componentName); bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false); connector.disconnect(); assertThat(bluetoothManager.mServiceConnection).isNull(); assertThat(bluetoothManager.mStateChangeCallback).isNull(); } }
service/src/com/android/server/bluetooth/BluetoothManagerService.java +2 −0 Original line number Diff line number Diff line Loading @@ -2037,7 +2037,9 @@ class BluetoothManagerService { IBluetoothStateChangeCallback unregCallback = (IBluetoothStateChangeCallback)msg.obj; try { // LINT.IfChange unregCallback.onBluetoothStateChange(false); // LINT.ThenChange(/framework/tests/unit/src/android/bluetooth/BluetoothProfileConnectorTest.java) } catch (RemoteException e) { Log.e(TAG, "UNREGISTER_STATE_CHANGE_CALLBACK: callback failed", e); } Loading