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

Commit 2cfa99a6 authored by William Escande's avatar William Escande
Browse files

Pan: Prevent native call from within the test

There is no check / no pre-requisite about the native state while
running a service test for instrumentations. Mocking the interface and
preventing the calls to be made should prevent the test to fail.

Note that there are still 2 calls to native being done: init and cleanup

Bug: 295103561
Bug: 294642435
Test: atest PanServiceTest | This was already passing when run alone
Test: atest BluetoothInstrumentationTests | Previous test were making it
      fail
Change-Id: I6aaeb9fd340cd9afd0616b1b478877efded00162
parent 4eeb2744
Loading
Loading
Loading
Loading
+7 −5
Original line number Diff line number Diff line
@@ -110,7 +110,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
static const bt_interface_t* btIf;

static void initializeNative(JNIEnv* env, jobject object) {
  debug("pan");
  debug("Initialize pan");
  if (btIf) return;

  btIf = getBluetoothInterface();
@@ -153,6 +153,7 @@ static void initializeNative(JNIEnv* env, jobject object) {
}

static void cleanupNative(JNIEnv* env, jobject object) {
  debug("Cleanup pan");
  if (!btIf) return;

  if (sPanIf != NULL) {
@@ -172,7 +173,7 @@ static void cleanupNative(JNIEnv* env, jobject object) {
static jboolean connectPanNative(JNIEnv* env, jobject object,
                                 jbyteArray address, jint src_role,
                                 jint dest_role) {
  debug("in");
  debug("Connect pan");
  if (!sPanIf) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, NULL);
@@ -194,6 +195,7 @@ static jboolean connectPanNative(JNIEnv* env, jobject object,

static jboolean disconnectPanNative(JNIEnv* env, jobject object,
                                    jbyteArray address) {
  debug("Disconnects pan");
  if (!sPanIf) return JNI_FALSE;

  jbyte* addr = env->GetByteArrayElements(address, NULL);
@@ -219,11 +221,11 @@ static JNINativeMethod sMethods[] = {
    {"cleanupNative", "()V", (void*)cleanupNative},
    {"connectPanNative", "([BII)Z", (void*)connectPanNative},
    {"disconnectPanNative", "([B)Z", (void*)disconnectPanNative},
    // TBD cleanup
};

int register_com_android_bluetooth_pan(JNIEnv* env) {
  return jniRegisterNativeMethods(env, "com/android/bluetooth/pan/PanService",
                                  sMethods, NELEM(sMethods));
  return jniRegisterNativeMethods(
      env, "com/android/bluetooth/pan/PanNativeInterface", sMethods,
      NELEM(sMethods));
}
}
+137 −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 com.android.bluetooth.pan;

import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

/** Provides Bluetooth Pan native interface for the Pan service */
public class PanNativeInterface {
    private static final String TAG = PanNativeInterface.class.getSimpleName();
    private PanService mPanService;

    @GuardedBy("INSTANCE_LOCK")
    private static PanNativeInterface sInstance;

    private static final Object INSTANCE_LOCK = new Object();

    static {
        if (Utils.isInstrumentationTestMode()) {
            Log.w(TAG, "App is instrumented. Skip loading the native");
        } else {
            classInitNative();
        }
    }

    private PanNativeInterface() {}

    /** Get singleton instance. */
    public static PanNativeInterface getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (sInstance == null) {
                sInstance = new PanNativeInterface();
            }
            return sInstance;
        }
    }

    /** Set singleton instance. */
    @VisibleForTesting
    public static void setInstance(PanNativeInterface instance) {
        synchronized (INSTANCE_LOCK) {
            sInstance = instance;
        }
    }

    void init(PanService panService) {
        mPanService = panService;
        initializeNative();
    }

    void cleanup() {
        cleanupNative();
    }

    boolean connect(byte[] identityAddress) {
        return connectPanNative(
                identityAddress, BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
    }

    boolean disconnect(byte[] identityAddress) {
        return disconnectPanNative(identityAddress);
    }

    /**********************************************************************************************/
    /*********************************** callbacks from native ************************************/
    /**********************************************************************************************/

    void onControlStateChanged(int localRole, int halState, int error, String ifname) {
        mPanService.onControlStateChanged(localRole, convertHalState(halState), error, ifname);
    }

    void onConnectStateChanged(
            byte[] address, int halState, int error, int localRole, int remoteRole) {
        mPanService.onConnectStateChanged(
                address, convertHalState(halState), error, localRole, remoteRole);
    }

    @VisibleForTesting
    static int convertHalState(int halState) {
        switch (halState) {
            case CONN_STATE_CONNECTED:
                return BluetoothProfile.STATE_CONNECTED;
            case CONN_STATE_CONNECTING:
                return BluetoothProfile.STATE_CONNECTING;
            case CONN_STATE_DISCONNECTED:
                return BluetoothProfile.STATE_DISCONNECTED;
            case CONN_STATE_DISCONNECTING:
                return BluetoothProfile.STATE_DISCONNECTING;
            default:
                Log.e(TAG, "Invalid pan connection state: " + halState);
                return BluetoothProfile.STATE_DISCONNECTED;
        }
    }

    /**********************************************************************************************/
    /******************************************* native *******************************************/
    /**********************************************************************************************/

    // Constants matching Hal header file bt_hh.h: bthh_connection_state_t

    @VisibleForTesting static final int CONN_STATE_CONNECTED = 0;

    @VisibleForTesting static final int CONN_STATE_CONNECTING = 1;

    @VisibleForTesting static final int CONN_STATE_DISCONNECTED = 2;

    @VisibleForTesting static final int CONN_STATE_DISCONNECTING = 3;

    private static native void classInitNative();

    private native void initializeNative();

    private native void cleanupNative();

    private native boolean connectPanNative(byte[] btAddress, int localRole, int remoteRole);

    private native boolean disconnectPanNative(byte[] btAddress);
}
+79 −102
Original line number Diff line number Diff line
@@ -94,17 +94,18 @@ public class PanService extends ProfileService {

    private AdapterService mAdapterService;

    @VisibleForTesting PanNativeInterface mNativeInterface;

    TetheringManager.TetheringEventCallback mTetheringCallback =
            new TetheringManager.TetheringEventCallback() {
                @Override
                public void onError(TetheringInterface iface, int error) {
                    if (mIsTethering
                            && iface.getType() == TetheringManager.TETHERING_BLUETOOTH) {
                    if (mIsTethering && iface.getType() == TetheringManager.TETHERING_BLUETOOTH) {
                        // tethering is fail because of @TetheringIfaceError error.
                        Log.e(TAG, "Error setting up tether interface: " + error);
                        for (Map.Entry device : mPanDevices.entrySet()) {
                            disconnectPanNative(Utils.getByteAddress(
                                    (BluetoothDevice) device.getKey()));
                            mNativeInterface.disconnect(
                                    Utils.getByteAddress((BluetoothDevice) device.getKey()));
                        }
                        mPanDevices.clear();
                        mIsTethering = false;
@@ -112,10 +113,6 @@ public class PanService extends ProfileService {
                }
            };

    static {
        classInitNative();
    }

    public static boolean isEnabled() {
        return BluetoothProperties.isProfilePanNapEnabled().orElse(false)
                || BluetoothProperties.isProfilePanPanuEnabled().orElse(false);
@@ -151,6 +148,10 @@ public class PanService extends ProfileService {
                "AdapterService cannot be null when PanService starts");
        mDatabaseManager = Objects.requireNonNull(AdapterService.getAdapterService().getDatabase(),
                "DatabaseManager cannot be null when PanService starts");
        mNativeInterface =
                Objects.requireNonNull(
                        PanNativeInterface.getInstance(),
                        "PanNativeInterface cannot be null when PanService starts");

        mBluetoothTetheringCallbacks = new HashMap<>();
        mPanDevices = new HashMap<BluetoothDevice, BluetoothPanDevice>();
@@ -160,8 +161,7 @@ public class PanService extends ProfileService {
        } catch (NotFoundException e) {
            mMaxPanDevices = BLUETOOTH_MAX_PAN_CONNECTIONS;
        }
        initializeNative();
        mNativeAvailable = true;
        mNativeInterface.init(this);

        mUserManager = getSystemService(UserManager.class);

@@ -184,6 +184,7 @@ public class PanService extends ProfileService {
            mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback);
            mTetheringManager = null;
        }
        mNativeInterface.cleanup();
        mHandler.removeCallbacksAndMessages(null);
        return true;
    }
@@ -192,10 +193,6 @@ public class PanService extends ProfileService {
    protected void cleanup() {
        // TODO(b/72948646): this should be moved to stop()
        setPanService(null);
        if (mNativeAvailable) {
            cleanupNative();
            mNativeAvailable = false;
        }

        mUserManager = null;

@@ -217,52 +214,71 @@ public class PanService extends ProfileService {
        }
    }

    private final Handler mHandler = new Handler() {
    private final Handler mHandler =
            new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    switch (msg.what) {
                case MESSAGE_CONNECT: {
                    BluetoothDevice device = (BluetoothDevice) msg.obj;
                    if (!connectPanNative(mAdapterService.getByteIdentityAddress(device),
                            BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE)) {
                        handlePanDeviceStateChange(device, null, BluetoothProfile.STATE_CONNECTING,
                                BluetoothPan.LOCAL_PANU_ROLE, BluetoothPan.REMOTE_NAP_ROLE);
                        handlePanDeviceStateChange(device, null,
                                BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
                        case MESSAGE_CONNECT:
                            BluetoothDevice connectDevice = (BluetoothDevice) msg.obj;
                            if (!mNativeInterface.connect(
                                    mAdapterService.getByteIdentityAddress(connectDevice))) {
                                handlePanDeviceStateChange(
                                        connectDevice,
                                        null,
                                        BluetoothProfile.STATE_CONNECTING,
                                        BluetoothPan.LOCAL_PANU_ROLE,
                                        BluetoothPan.REMOTE_NAP_ROLE);
                                handlePanDeviceStateChange(
                                        connectDevice,
                                        null,
                                        BluetoothProfile.STATE_DISCONNECTED,
                                        BluetoothPan.LOCAL_PANU_ROLE,
                                        BluetoothPan.REMOTE_NAP_ROLE);
                        break;
                    }
                            }
                            break;
                case MESSAGE_DISCONNECT: {
                    BluetoothDevice device = (BluetoothDevice) msg.obj;
                    if (!disconnectPanNative(mAdapterService.getByteIdentityAddress(device))) {
                        handlePanDeviceStateChange(device, mPanIfName,
                                BluetoothProfile.STATE_DISCONNECTING, BluetoothPan.LOCAL_PANU_ROLE,
                        case MESSAGE_DISCONNECT:
                            BluetoothDevice disconnectDevice = (BluetoothDevice) msg.obj;
                            if (!mNativeInterface.disconnect(
                                    mAdapterService.getByteIdentityAddress(disconnectDevice))) {
                                handlePanDeviceStateChange(
                                        disconnectDevice,
                                        mPanIfName,
                                        BluetoothProfile.STATE_DISCONNECTING,
                                        BluetoothPan.LOCAL_PANU_ROLE,
                                        BluetoothPan.REMOTE_NAP_ROLE);
                        handlePanDeviceStateChange(device, mPanIfName,
                                BluetoothProfile.STATE_DISCONNECTED, BluetoothPan.LOCAL_PANU_ROLE,
                                handlePanDeviceStateChange(
                                        disconnectDevice,
                                        mPanIfName,
                                        BluetoothProfile.STATE_DISCONNECTED,
                                        BluetoothPan.LOCAL_PANU_ROLE,
                                        BluetoothPan.REMOTE_NAP_ROLE);
                        break;
                    }
                            }
                            break;
                case MESSAGE_CONNECT_STATE_CHANGED: {
                        case MESSAGE_CONNECT_STATE_CHANGED:
                            ConnectState cs = (ConnectState) msg.obj;
                    final BluetoothDevice device = mAdapterService.getDeviceFromByte(cs.addr);
                            final BluetoothDevice device =
                                    mAdapterService.getDeviceFromByte(cs.addr);
                            // TBD get iface from the msg
                            if (DBG) {
                        Log.d(TAG,
                                "MESSAGE_CONNECT_STATE_CHANGED: " + device + " state: " + cs.state);
                    }
                    // It could be null if the connection up is coming when the Bluetooth is turning
                    // off.
                                Log.d(
                                        TAG,
                                        "MESSAGE_CONNECT_STATE_CHANGED: "
                                                + device
                                                + " state: "
                                                + cs.state);
                            }
                            // It could be null if the connection up is coming when the
                            // Bluetooth is turning off.
                            if (device == null) {
                                break;
                            }
                    handlePanDeviceStateChange(device, mPanIfName /* iface */,
                            convertHalState(cs.state), cs.local_role, cs.remote_role);
                }
                            handlePanDeviceStateChange(
                                    device,
                                    mPanIfName /* iface */,
                                    cs.state,
                                    cs.local_role,
                                    cs.remote_role);
                            break;
                    }
                }
@@ -603,7 +619,6 @@ public class PanService extends ProfileService {
        public int remote_role;
    }

    @VisibleForTesting
    void onConnectStateChanged(byte[] address, int state, int error, int localRole,
            int remoteRole) {
        if (DBG) {
@@ -626,22 +641,6 @@ public class PanService extends ProfileService {
        }
    }

    @VisibleForTesting
    static int convertHalState(int halState) {
        switch (halState) {
            case CONN_STATE_CONNECTED:
                return BluetoothProfile.STATE_CONNECTED;
            case CONN_STATE_CONNECTING:
                return BluetoothProfile.STATE_CONNECTING;
            case CONN_STATE_DISCONNECTED:
                return BluetoothProfile.STATE_DISCONNECTED;
            case CONN_STATE_DISCONNECTING:
                return BluetoothProfile.STATE_DISCONNECTING;
            default:
                Log.e(TAG, "bad pan connection state: " + halState);
                return BluetoothProfile.STATE_DISCONNECTED;
        }
    }

    void handlePanDeviceStateChange(BluetoothDevice device, String iface, int state,
            @LocalPanRole int localRole, @RemotePanRole int remoteRole) {
@@ -688,7 +687,7 @@ public class PanService extends ProfileService {
                    Log.d(TAG, "handlePanDeviceStateChange BT tethering is off/Local role"
                            + " is PANU drop the connection");
                    mPanDevices.remove(device);
                    disconnectPanNative(Utils.getByteAddress(device));
                    mNativeInterface.disconnect(Utils.getByteAddress(device));
                    return;
                }
                Log.d(TAG, "handlePanDeviceStateChange LOCAL_NAP_ROLE:REMOTE_PANU_ROLE");
@@ -776,26 +775,4 @@ public class PanService extends ProfileService {
            mRemoteRole = remoteRole;
        }
    }

    // Constants matching Hal header file bt_hh.h
    // bthh_connection_state_t
    @VisibleForTesting
    static final int CONN_STATE_CONNECTED = 0;
    @VisibleForTesting
    static final int CONN_STATE_CONNECTING = 1;
    @VisibleForTesting
    static final int CONN_STATE_DISCONNECTED = 2;
    @VisibleForTesting
    static final int CONN_STATE_DISCONNECTING = 3;

    private static native void classInitNative();

    private native void initializeNative();

    private native void cleanupNative();

    private native boolean connectPanNative(byte[] btAddress, int localRole, int remoteRole);

    private native boolean disconnectPanNative(byte[] btAddress);

}
+7 −2
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.gatt.GattService;
import com.android.bluetooth.pan.PanNativeInterface;

import org.junit.After;
import org.junit.Assert;
@@ -76,12 +77,14 @@ public class ProfileServiceTest {
            mStartedProfileMap.put(profile.getSimpleName(), false);
        }
        Intent startIntent = new Intent(InstrumentationRegistry.getTargetContext(), profile);
        startIntent.putExtra(AdapterService.EXTRA_ACTION,
                AdapterService.ACTION_SERVICE_STATE_CHANGED);
        startIntent.putExtra(
                AdapterService.EXTRA_ACTION, AdapterService.ACTION_SERVICE_STATE_CHANGED);
        startIntent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
        mServiceTestRule.startService(startIntent);
    }

    @Mock private PanNativeInterface mPanNativeInterface;

    private void setAllProfilesState(int state, int invocationNumber) throws TimeoutException {
        int profileCount = mProfiles.length;
        for (Class profile : mProfiles) {
@@ -160,6 +163,7 @@ public class ProfileServiceTest {
                false /* is_common_criteria_mode */, 0 /* config_compare_result */,
                new String[0], false, "");
        mMockAdapterService.enableNative();
        PanNativeInterface.setInstance(mPanNativeInterface);
    }

    @After
@@ -171,6 +175,7 @@ public class ProfileServiceTest {
        TestUtils.clearAdapterService(mMockAdapterService);
        mMockAdapterService = null;
        mProfiles = null;
        PanNativeInterface.setInstance(null);
    }

    /**
+9 −6
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.pan.PanService.BluetoothPanDevice;

import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,6 +62,7 @@ public class PanServiceTest {

    @Mock private AdapterService mAdapterService;
    @Mock private DatabaseManager mDatabaseManager;
    @Mock private PanNativeInterface mNativeInterface;
    @Mock private UserManager mMockUserManager;

    @Before
@@ -71,8 +71,10 @@ public class PanServiceTest {
        TestUtils.setAdapterService(mAdapterService);
        doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
        doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
        PanNativeInterface.setInstance(mNativeInterface);
        TestUtils.startService(mServiceRule, PanService.class);
        mService = PanService.getPanService();

        assertThat(mService).isNotNull();
        // Try getting the Bluetooth adapter
        mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -84,6 +86,7 @@ public class PanServiceTest {
    @After
    public void tearDown() throws Exception {
        TestUtils.stopService(mServiceRule, PanService.class);
        PanNativeInterface.setInstance(null);
        mService = PanService.getPanService();
        assertThat(mService).isNull();
        TestUtils.clearAdapterService(mAdapterService);
@@ -125,15 +128,15 @@ public class PanServiceTest {

    @Test
    public void convertHalState() {
        assertThat(PanService.convertHalState(PanService.CONN_STATE_CONNECTED))
        assertThat(PanNativeInterface.convertHalState(PanNativeInterface.CONN_STATE_CONNECTED))
                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
        assertThat(PanService.convertHalState(PanService.CONN_STATE_CONNECTING))
        assertThat(PanNativeInterface.convertHalState(PanNativeInterface.CONN_STATE_CONNECTING))
                .isEqualTo(BluetoothProfile.STATE_CONNECTING);
        assertThat(PanService.convertHalState(PanService.CONN_STATE_DISCONNECTED))
        assertThat(PanNativeInterface.convertHalState(PanNativeInterface.CONN_STATE_DISCONNECTED))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
        assertThat(PanService.convertHalState(PanService.CONN_STATE_DISCONNECTING))
        assertThat(PanNativeInterface.convertHalState(PanNativeInterface.CONN_STATE_DISCONNECTING))
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
        assertThat(PanService.convertHalState(-24664)) // illegal value
        assertThat(PanNativeInterface.convertHalState(-24664)) // illegal value
                .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
    }