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

Commit 876ebbd4 authored by William Escande's avatar William Escande
Browse files

SystemServer: Respect ble scan mode

Bug: 341783936
Test: atest ServiceBluetoothRoboTests
Flag: com.android.bluetooth.flags.respect_ble_scan_setting
Change-Id: I6dcfaebfc8094db0fdc378cd22562d4238dcbf64
parent 32fa7bd3
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ java_library {
        "src/AdapterBinder.kt",
        "src/AdapterState.kt",
        "src/AutoOnFeature.kt",
        "src/BleScanSettingListener.kt",
        "src/BluetoothService.kt",
        "src/Log.kt",
        "src/RadioModeListener.kt",
@@ -171,6 +172,8 @@ android_robolectric_test {
        "src/AdapterStateTest.kt",
        "src/AutoOnFeature.kt",
        "src/AutoOnFeatureTest.kt",
        "src/BleScanSettingListener.kt",
        "src/BleScanSettingListenerTest.kt",
        "src/Log.kt",
        "src/LogTest.kt",
        "src/RadioModeListener.kt",
+72 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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.
 */

// @file:JvmName("BleScanSettingListener")

package com.android.server.bluetooth

import android.content.ContentResolver
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
import android.provider.Settings

private const val TAG = "BleScanSettingListener"

object BleScanSettingListener {
    @JvmStatic
    var isScanAllowed = false
        private set

    /**
     * Listen on Ble Scan setting and trigger the callback when scanning is no longer enabled
     *
     * @param callback: The callback to trigger when there is a mode change, pass new mode as
     *   parameter
     * @return The initial value of the radio
     */
    @JvmStatic
    fun initialize(looper: Looper, resolver: ContentResolver, callback: () -> Unit) {
        val notifyForDescendants = false

        resolver.registerContentObserver(
            Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE),
            notifyForDescendants,
            object : ContentObserver(Handler(looper)) {
                override fun onChange(selfChange: Boolean) {
                    isScanAllowed = getScanSettingValue(resolver)
                    if (isScanAllowed) {
                        Log.i(TAG, "Ble Scan mode is now allowed. Nothing to do")
                        return
                    } else {
                        Log.i(TAG, "Trigger callback to disable BLE_ONLY mode")
                        callback()
                    }
                }
            }
        )
        isScanAllowed = getScanSettingValue(resolver)
    }

    /**
     * Check if Bluetooth is impacted by the radio and fetch global mode status
     *
     * @return whether Bluetooth should consider this radio or not
     */
    private fun getScanSettingValue(resolver: ContentResolver): Boolean {
        return Settings.Global.getInt(resolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE) != 0
    }
}
+124 −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.server.bluetooth.test

import android.content.ContentResolver
import android.content.Context
import android.os.Looper
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
import com.android.server.bluetooth.BleScanSettingListener.initialize
import com.android.server.bluetooth.BleScanSettingListener.isScanAllowed
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf

@RunWith(RobolectricTestRunner::class)
class BleScanSettingListenerTest {
    private val resolver: ContentResolver =
        ApplicationProvider.getApplicationContext<Context>().getContentResolver()

    private val looper: Looper = Looper.getMainLooper()

    private var callbackTriggered: Boolean = false

    @Before
    public fun setup() {
        callbackTriggered = false
        enableSetting()
    }

    private fun startListener() {
        initialize(looper, resolver, this::callback)
    }

    private fun enableSetting() {
        Settings.Global.putInt(resolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 1)
        shadowOf(looper).idle()
    }

    private fun disableSetting() {
        Settings.Global.putInt(resolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE, 0)
        shadowOf(looper).idle()
    }

    private fun callback() {
        callbackTriggered = true
    }

    @Test
    fun initialize_whenSettingsOff_isOff() {
        disableSetting()

        startListener()

        assertThat(isScanAllowed).isFalse()
        assertThat(callbackTriggered).isFalse()
    }

    @Test
    fun initialize_whenSettingsOn_isScanAllowed() {
        startListener()

        assertThat(isScanAllowed).isTrue()
        assertThat(callbackTriggered).isFalse()
    }

    @Test
    fun changeModeToOn_whenSettingsOn_isScanAllowedAndEventDiscarded() {
        startListener()

        enableSetting()

        assertThat(isScanAllowed).isTrue()
        assertThat(callbackTriggered).isFalse()
    }

    @Test
    fun changeModeToOff_whenSettingsOff_isOffAndEventDiscarded() {
        disableSetting()
        startListener()

        disableSetting()

        assertThat(isScanAllowed).isFalse()
        assertThat(callbackTriggered).isFalse()
    }

    @Test
    fun changeModeToOn_whenSettingsOff_isScanAllowedWithoutCallback() {
        disableSetting()
        startListener()

        enableSetting()

        assertThat(isScanAllowed).isTrue()
        assertThat(callbackTriggered).isFalse()
    }

    @Test
    fun changeModeToOff_whenSettingsOn_isOffAndCallbackTriggered() {
        startListener()

        disableSetting()

        assertThat(isScanAllowed).isFalse()
        assertThat(callbackTriggered).isTrue()
    }
}
+46 −5
Original line number Diff line number Diff line
@@ -274,8 +274,7 @@ class BluetoothManagerService {
    private boolean mEnableExternal = false;

    // Map of apps registered to keep BLE scanning on.
    private Map<IBinder, ClientDeathRecipient> mBleApps =
            new ConcurrentHashMap<IBinder, ClientDeathRecipient>();
    private Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<>();

    private final BluetoothAdapterState mState = new BluetoothAdapterState();

@@ -630,7 +629,11 @@ class BluetoothManagerService {
        mHandler = new BluetoothHandler(mLooper);

        // Observe BLE scan only mode settings change.
        if (Flags.respectBleScanSetting()) {
            BleScanSettingListener.initialize(mLooper, mContentResolver, this::onBleScanDisabled);
        } else {
            registerForBleScanModeChange();
        }

        // Disable ASHA if BLE is not supported, overriding any system property
        if (!isBleSupported(mContext)) {
@@ -703,6 +706,30 @@ class BluetoothManagerService {
        }
    }

    private Unit onBleScanDisabled() {
        if (mState.oneOf(STATE_OFF, STATE_BLE_TURNING_OFF)) {
            Log.i(TAG, "onBleScanDisabled: Nothing to do, Bluetooth is already turning off");
            return Unit.INSTANCE;
        }
        clearBleApps();
        try {
            mAdapter.unregAllGattClient(mContext.getAttributionSource());
        } catch (RemoteException e) {
            Log.e(TAG, "onBleScanDisabled: unregAllGattClient failed", e);
        }
        if (mState.oneOf(STATE_BLE_ON)) {
            Log.i(TAG, "onBleScanDisabled: Shutting down BLE_ON mode");
            try {
                mAdapter.stopBle(mContext.getAttributionSource());
            } catch (RemoteException e) {
                Log.e(TAG, "onBleScanDisabled: stopBle failed", e);
            }
        } else {
            Log.i(TAG, "onBleScanDisabled: Bluetooth is not in BLE_ON, staying on");
        }
        return Unit.INSTANCE;
    }

    IBluetoothManager.Stub getBinder() {
        return mBinder;
    }
@@ -885,6 +912,12 @@ class BluetoothManagerService {
                return false;
            }
        }
        if (Flags.respectBleScanSetting()) {
            if (SatelliteModeListener.isOn()) {
                return false;
            }
            return BleScanSettingListener.isScanAllowed();
        }
        try {
            return Settings.Global.getInt(
                            mContentResolver, Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE)
@@ -1003,6 +1036,11 @@ class BluetoothManagerService {
            return false;
        }

        if (Flags.respectBleScanSetting() && !BleScanSettingListener.isScanAllowed()) {
            Log.d(TAG, "enableBle: not enabling - Scan mode is not allowed.");
            return false;
        }

        // TODO(b/262605980): enableBle/disableBle should be on handler thread
        updateBleAppCount(token, true, packageName);

@@ -1030,7 +1068,8 @@ class BluetoothManagerService {
                        + (" isBinding=" + isBinding())
                        + (" mState=" + mState));

        if (isSatelliteModeOn()) {
        // Remove this with flag, preventing a "disable" make no sense, even in satellite mode
        if (!Flags.respectBleScanSetting() && isSatelliteModeOn()) {
            Log.d(TAG, "disableBle: not disabling - satellite mode is on.");
            return false;
        }
@@ -1111,7 +1150,9 @@ class BluetoothManagerService {
            }
            boolean airplaneDoesNotAllowBleOn =
                    Flags.airplaneModeXBleOn() && AirplaneModeListener.isOn();
            if (!airplaneDoesNotAllowBleOn && isBleAppPresent()) {
            boolean scanIsAllowed =
                    !Flags.respectBleScanSetting() || BleScanSettingListener.isScanAllowed();
            if (!airplaneDoesNotAllowBleOn && isBleAppPresent() && scanIsAllowed) {
                // Need to stay at BLE ON. Disconnect all Gatt connections
                Log.i(TAG, "sendBrEdrDownCallback: Staying in BLE_ON");
                mAdapter.unregAllGattClient(mContext.getAttributionSource());