Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15d91efc authored by David Duarte's avatar David Duarte Committed by Gerrit Code Review
Browse files

Merge "BluetoothProfileConnector: Always unbind when Bluetooth is off" into main

parents 3512273d 79446c55
Loading
Loading
Loading
Loading
+41 −41
Original line number Diff line number Diff line
@@ -32,13 +32,13 @@ import android.util.Log;
import java.util.Objects;

/**
 * Connector for Bluetooth profile proxies to bind manager service and
 * profile services
 * Connector for Bluetooth profile proxies to bind manager service and profile services
 *
 * @param <T> The Bluetooth profile interface for this connection.
 * @hide
 */
@SuppressLint("AndroidFrameworkBluetoothPermission")
public abstract class BluetoothProfileConnector<T> {
public abstract class BluetoothProfileConnector<T> extends Handler {
    private final CloseGuard mCloseGuard = new CloseGuard();
    private final int mProfileId;
    private BluetoothProfile.ServiceListener mServiceListener;
@@ -48,6 +48,7 @@ public abstract class BluetoothProfileConnector<T> {
    private final String mServiceName;
    private final IBluetoothManager mBluetoothManager;
    private volatile T mService;
    private boolean mBound = false;

    private static final int MESSAGE_SERVICE_CONNECTED = 100;
    private static final int MESSAGE_SERVICE_DISCONNECTED = 101;
@@ -69,26 +70,29 @@ public abstract class BluetoothProfileConnector<T> {
                public void onServiceConnected(ComponentName className, IBinder service) {
                    logDebug("Proxy object connected");
                    mService = getServiceInterface(service);
            mHandler.sendMessage(mHandler.obtainMessage(
                    MESSAGE_SERVICE_CONNECTED));
                    sendEmptyMessage(MESSAGE_SERVICE_CONNECTED);
                }

                @Override
                public void onServiceDisconnected(ComponentName className) {
                    logDebug("Proxy object disconnected");
                    T service = mService;
                    doUnbind();
            mHandler.sendMessage(mHandler.obtainMessage(
                    MESSAGE_SERVICE_DISCONNECTED));
                    if (service != null) {
                        sendEmptyMessage(MESSAGE_SERVICE_DISCONNECTED);
                    }
                }
            };

    /** @hide */
    public BluetoothProfileConnector(
            Looper looper,
            BluetoothProfile profile,
            int profileId,
            String profileName,
            String serviceName,
            IBluetoothManager bluetoothManager) {
        super(looper);
        mProfileId = profileId;
        mProfileProxy = profile;
        mProfileName = profileName;
@@ -99,6 +103,7 @@ public abstract class BluetoothProfileConnector<T> {
    BluetoothProfileConnector(
            BluetoothProfile profile, int profileId, String profileName, String serviceName) {
        this(
                Looper.getMainLooper(),
                profile,
                profileId,
                profileName,
@@ -113,30 +118,30 @@ public abstract class BluetoothProfileConnector<T> {
        doUnbind();
    }

    private boolean doBind() {
    private void doBind() {
        synchronized (mConnection) {
            if (mService == null) {
            if (!mBound) {
                logDebug("Binding service for " + mPackageName);
                mCloseGuard.open("doUnbind");
                try {
                    return mBluetoothManager.bindBluetoothProfileService(
                    mBluetoothManager.bindBluetoothProfileService(
                            mProfileId, mServiceName, mConnection);
                    mBound = true;
                } catch (RemoteException re) {
                    logError("Failed to bind service. " + re);
                    return false;
                }
            }
        }
        return true;
    }

    private void doUnbind() {
        synchronized (mConnection) {
            if (mService != null) {
            if (mBound) {
                logDebug("Unbinding service for " + mPackageName);
                mCloseGuard.close();
                try {
                    mBluetoothManager.unbindBluetoothProfileService(mProfileId, mConnection);
                    mBound = false;
                } catch (RemoteException re) {
                    logError("Unable to unbind service: " + re);
                } finally {
@@ -206,18 +211,15 @@ public abstract class BluetoothProfileConnector<T> {
        Log.e(mProfileName, log);
    }

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
                case MESSAGE_SERVICE_CONNECTED: {
            case MESSAGE_SERVICE_CONNECTED:
                if (mServiceListener != null) {
                    mServiceListener.onServiceConnected(mProfileId, mProfileProxy);
                }
                break;
                }
                case MESSAGE_SERVICE_DISCONNECTED: {
            case MESSAGE_SERVICE_DISCONNECTED:
                if (mServiceListener != null) {
                    mServiceListener.onServiceDisconnected(mProfileId);
                }
@@ -225,5 +227,3 @@ public abstract class BluetoothProfileConnector<T> {
        }
    }
}
    };
}
+2 −0
Original line number Diff line number Diff line
@@ -23,7 +23,9 @@ android_test {
    static_libs: [
        "androidx.test.ext.truth",
        "androidx.test.rules",
        "frameworks-base-testutils",
        "junit",
        "mockito-target",
        "modules-utils-bytesmatcher",
    ],
    test_suites: [
+82 −18
Original line number Diff line number Diff line
@@ -18,16 +18,27 @@ package android.bluetooth;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;

import android.content.ComponentName;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.test.TestLooper;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;

/** Test cases for {@link BluetoothProfileConnector}. */
@SmallTest
@@ -36,6 +47,15 @@ public class BluetoothProfileConnectorTest {
    static class FakeBluetoothManager extends IBluetoothManager.Default {
        private IBluetoothStateChangeCallback mStateChangeCallback;
        private IBluetoothProfileServiceConnection mServiceConnection;
        private final Handler mHandler;

        private FakeBluetoothManager(Looper looper) {
            mHandler = new Handler(looper);
        }

        Looper getLooper() {
            return mHandler.getLooper();
        }

        @Override
        public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
@@ -64,14 +84,28 @@ public class BluetoothProfileConnectorTest {
                int profile, IBluetoothProfileServiceConnection proxy) {
            if (proxy != mServiceConnection) throw new IllegalStateException();

            mHandler.post(
                    () -> {
                        try {
                            proxy.onServiceDisconnected(new ComponentName("pkg", "cls"));
                        } catch (RemoteException e) {
                            throw new RuntimeException(e);
                        }
                    });

            mServiceConnection = null;
        }
    }

    private BluetoothProfileConnector createBluetoothProfileConnector(
            IBluetoothManager bluetoothManager) {
            FakeBluetoothManager bluetoothManager) {
        return new BluetoothProfileConnector(
                null, BluetoothProfile.HEADSET, "Headset", "HeadsetService", bluetoothManager) {
                bluetoothManager.getLooper(),
                null,
                BluetoothProfile.HEADSET,
                "Headset",
                "HeadsetService",
                bluetoothManager) {
            public IBinder getServiceInterface(IBinder service) {
                return service;
            }
@@ -80,89 +114,119 @@ public class BluetoothProfileConnectorTest {

    @Test
    public void bind_registerServiceConnection() throws RemoteException {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true);
        looper.dispatchAll();

        assertThat(bluetoothManager.mServiceConnection).isNotNull();
        verifyZeroInteractions(listener);
    }

    @Test
    public void unbind_unregisterServiceConnection() throws RemoteException {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        ComponentName componentName = new ComponentName("pkg", "cls");
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true);
        bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder());
        bluetoothManager.mServiceConnection.onServiceDisconnected(componentName);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false);
        looper.dispatchAll();

        assertThat(bluetoothManager.mServiceConnection).isNull();
        InOrder order = inOrder(listener);
        order.verify(listener).onServiceConnected(anyInt(), any());
        order.verify(listener).onServiceDisconnected(anyInt());
        order.verifyNoMoreInteractions();
    }

    @Test
    public void upThenDown_unregisterServiceConnection() throws RemoteException {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false);
        looper.dispatchAll();

        // TODO(b/302092694): Should be isNull
        assertThat(bluetoothManager.mServiceConnection).isNotNull();
        assertThat(bluetoothManager.mServiceConnection).isNull();
        verifyZeroInteractions(listener);
    }

    @Test
    public void disconnectAfterConnect_unregisterCallbacks() {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        connector.disconnect();
        looper.dispatchAll();

        assertThat(bluetoothManager.mServiceConnection).isNull();
        assertThat(bluetoothManager.mStateChangeCallback).isNull();
        InOrder order = inOrder(listener);
        // TODO(b/309635805): This should not be here
        order.verify(listener).onServiceDisconnected(anyInt());
        order.verifyNoMoreInteractions();
    }

    @Test
    public void disconnectAfterBind_unregisterCallbacks() throws RemoteException {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true);
        connector.disconnect();
        looper.dispatchAll();

        // TODO(b/302092694): Should be isNull
        assertThat(bluetoothManager.mServiceConnection).isNotNull();
        assertThat(bluetoothManager.mServiceConnection).isNull();
        assertThat(bluetoothManager.mStateChangeCallback).isNull();
        InOrder order = inOrder(listener);
        // TODO(b/309635805): This should not be here
        order.verify(listener).onServiceDisconnected(anyInt());
        order.verifyNoMoreInteractions();
    }

    @Test
    public void disconnectAfterUnbind_unregisterCallbacks() throws RemoteException {
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager();
        TestLooper looper = new TestLooper();
        FakeBluetoothManager bluetoothManager = new FakeBluetoothManager(looper.getLooper());
        BluetoothProfileConnector connector = createBluetoothProfileConnector(bluetoothManager);
        ComponentName componentName = new ComponentName("pkg", "cls");
        BluetoothProfile.ServiceListener listener = null;
        BluetoothProfile.ServiceListener listener = mock(BluetoothProfile.ServiceListener.class);

        connector.connect("test.package", listener);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(true);
        bluetoothManager.mServiceConnection.onServiceConnected(componentName, new Binder());
        bluetoothManager.mServiceConnection.onServiceDisconnected(componentName);
        bluetoothManager.mStateChangeCallback.onBluetoothStateChange(false);
        looper.dispatchAll();
        connector.disconnect();
        looper.dispatchAll();

        assertThat(bluetoothManager.mServiceConnection).isNull();
        assertThat(bluetoothManager.mStateChangeCallback).isNull();
        InOrder order = inOrder(listener);
        order.verify(listener).onServiceConnected(anyInt(), any());
        // TODO(b/309635805): Should be only one
        order.verify(listener, times(2)).onServiceDisconnected(anyInt());
        order.verifyNoMoreInteractions();
    }
}