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

Commit 9d2d4e22 authored by David Duarte's avatar David Duarte
Browse files

Add BluetoothProfileConnectorTest

This contains also some failing tests for b/302092694

Bug: 302092694
Test: atest BluetoothProfileConnectorTest
Change-Id: I876f0cde2777977e40bb9c3ef759c50839f57694
parent 15b3c273
Loading
Loading
Loading
Loading
+45 −27
Original line number Diff line number Diff line
@@ -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
@@ -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;
@@ -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} */
@@ -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;
@@ -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 {
@@ -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
@@ -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;
+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();
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -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);
                    }