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

Commit 48ef6e02 authored by William Escande's avatar William Escande
Browse files

Implement SatelliteModeListener in kt + FLAG

* Set up feature behind a flag
* No longer require a specific class to hold it
* fix async issue by creating the observer with the bms looper
* fix init slow down by registering and reading settings on the bms
  thread
* add test to check all expected behaviors

Bug: 286260653
Bug: 262605980
Bug: 289584302
Bug: 286602847
Test: m service-bluetooth
Test: atest ServiceBluetoothRoboTests
Change-Id: Ic1741d77094d41e8718bc773a2266ee1e8998fa1
parent 8b27b44e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ filegroup {
        "src/AdapterState.kt",
        "src/RadioModeListener.kt",
        "src/com/**/*.kt",
        "src/satellite/ModeListener.kt",
    ],
    visibility: [":__subpackages__"],
}
@@ -164,6 +165,8 @@ android_robolectric_test {
        "src/AdapterStateTest.kt",
        "src/RadioModeListener.kt",
        "src/RadioModeListenerTest.kt",
        "src/satellite/ModeListener.kt",
        "src/satellite/ModeListenerTest.kt",
    ],

    static_libs: [
+32 −6
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.sysprop.BluetoothProperties;
@@ -84,6 +85,9 @@ import com.android.bluetooth.BluetoothStatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.BluetoothManagerServiceDumpProto;
import com.android.server.bluetooth.satellite.SatelliteModeListener;

import kotlin.Unit;

import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -183,6 +187,7 @@ class BluetoothManagerService {
    private static final int DEFAULT_APM_ENHANCEMENT_STATE = 1;

    private final Context mContext;
    private final Looper mLooper;

    private final UserManager mUserManager;

@@ -215,8 +220,16 @@ class BluetoothManagerService {

    private BluetoothNotificationManager mBluetoothNotificationManager;

    // TODO(b/289584302): remove BluetoothSatelliteModeListener once use_new_satellite_mode ship
    private BluetoothSatelliteModeListener mBluetoothSatelliteModeListener;

    // TODO(b/289584302): Use aconfig flag when available on AOSP
    private static final boolean USE_NEW_SATELLITE_MODE =
            DeviceConfig.getBoolean(
                    DeviceConfig.NAMESPACE_BLUETOOTH,
                    "com.android.bluetooth.use_new_satellite_mode",
                    false);

    // used inside handler thread
    private boolean mQuietEnable = false;
    private boolean mEnable = false;
@@ -443,8 +456,9 @@ class BluetoothManagerService {
                0);
    }

    // TODO(b/289584302): Update to private once use_new_satellite_mode is enabled
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    void onSatelliteModeChanged(boolean isSatelliteModeOn) {
    Unit onSatelliteModeChanged(boolean isSatelliteModeOn) {
        mHandler.postDelayed(
                () ->
                        delayModeChangedIfNeeded(
@@ -453,6 +467,7 @@ class BluetoothManagerService {
                                "onSatelliteModeChanged"),
                ON_SATELLITE_MODE_CHANGED_TOKEN,
                0);
        return Unit.INSTANCE;
    }

    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
@@ -640,7 +655,7 @@ class BluetoothManagerService {

    BluetoothManagerService(@NonNull Context context, @NonNull Looper looper) {
        mContext = requireNonNull(context, "Context cannot be null");
        requireNonNull(looper, "Looper cannot be null");
        mLooper = requireNonNull(looper, "Looper cannot be null");

        mUserManager =
                requireNonNull(
@@ -648,7 +663,7 @@ class BluetoothManagerService {
                        "UserManager system service cannot be null");

        mBinder = new BluetoothServiceBinder(this, mContext, mUserManager);
        mHandler = new BluetoothHandler(looper);
        mHandler = new BluetoothHandler(mLooper);

        mContentResolver = mContext.getContentResolver();

@@ -723,11 +738,13 @@ class BluetoothManagerService {
                || airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH)) {
            mBluetoothAirplaneModeListener =
                    new BluetoothAirplaneModeListener(
                            this, looper, mContext, mBluetoothNotificationManager);
                            this, mLooper, mContext, mBluetoothNotificationManager);
        }

        if (!USE_NEW_SATELLITE_MODE) {
            mBluetoothSatelliteModeListener =
                new BluetoothSatelliteModeListener(this, looper, mContext);
                    new BluetoothSatelliteModeListener(this, mLooper, mContext);
        }
    }

    IBluetoothManager.Stub getBinder() {
@@ -742,6 +759,9 @@ class BluetoothManagerService {

    /** Returns true if satellite mode is turned on. */
    private boolean isSatelliteModeOn() {
        if (USE_NEW_SATELLITE_MODE) {
            return SatelliteModeListener.isOn();
        }
        return mBluetoothSatelliteModeListener.isSatelliteModeOn();
    }

@@ -1486,6 +1506,12 @@ class BluetoothManagerService {
        if (DBG) {
            Log.d(TAG, "Bluetooth boot completed");
        }

        if (USE_NEW_SATELLITE_MODE) {
            SatelliteModeListener.initialize(
                    mLooper, mContentResolver, this::onSatelliteModeChanged);
        }

        final boolean isBluetoothDisallowed = isBluetoothDisallowed();
        if (isBluetoothDisallowed) {
            return;
+64 −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.
 */
@file:JvmName("SatelliteModeListener")

package com.android.server.bluetooth.satellite

import android.content.ContentResolver
import android.os.Looper
import android.util.Log
import com.android.server.bluetooth.initializeRadioModeListener

/**
 * constant copied from {@link Settings.Global}
 *
 * TODO(b/274636414): Migrate to official API in Android V.
 */
internal const val SETTINGS_SATELLITE_MODE_RADIOS = "satellite_mode_radios"

/**
 * constant copied from {@link Settings.Global}
 *
 * TODO(b/274636414): Migrate to official API in Android V.
 */
internal const val SETTINGS_SATELLITE_MODE_ENABLED = "satellite_mode_enabled"

private const val TAG = "BluetoothSatelliteModeListener"

public var isOn = false
    private set

/** Listen on satellite mode and trigger the callback if it has changed */
public fun initialize(looper: Looper, resolver: ContentResolver, callback: (m: Boolean) -> Unit) {
    val satellite_callback =
        fun(newMode: Boolean) {
            val previousMode = isOn
            isOn = newMode
            if (previousMode == isOn) {
                Log.d(TAG, "Ignore satellite mode change because is already: " + isOn)
                return
            }
            callback(isOn)
        }
    isOn =
        initializeRadioModeListener(
            looper,
            resolver,
            SETTINGS_SATELLITE_MODE_RADIOS,
            SETTINGS_SATELLITE_MODE_ENABLED,
            satellite_callback
        )
}
+195 −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.satellite.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.satellite.SETTINGS_SATELLITE_MODE_ENABLED
import com.android.server.bluetooth.satellite.SETTINGS_SATELLITE_MODE_RADIOS
import com.android.server.bluetooth.satellite.initialize
import com.android.server.bluetooth.satellite.isOn
import com.android.server.bluetooth.test.disableMode
import com.android.server.bluetooth.test.disableSensitive
import com.android.server.bluetooth.test.enableMode
import com.android.server.bluetooth.test.enableSensitive
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.robolectric.RobolectricTestRunner

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

    private val looper: Looper = Looper.getMainLooper()

    private lateinit var mode: ArrayList<Boolean>

    @Before
    public fun setup() {
        mode = ArrayList()
    }

    private fun enableSensitive() {
        enableSensitive(resolver, looper, SETTINGS_SATELLITE_MODE_RADIOS)
    }

    private fun disableSensitive() {
        disableSensitive(resolver, looper, SETTINGS_SATELLITE_MODE_RADIOS)
    }

    private fun disableMode() {
        disableMode(resolver, looper, SETTINGS_SATELLITE_MODE_ENABLED)
    }

    private fun enableMode() {
        enableMode(resolver, looper, SETTINGS_SATELLITE_MODE_ENABLED)
    }

    private fun callback(newMode: Boolean) = mode.add(newMode)

    @Test
    fun initialize_whenNullSensitive_isOff() {
        Settings.Global.putString(resolver, SETTINGS_SATELLITE_MODE_RADIOS, null)
        enableMode()

        initialize(looper, resolver, this::callback)

        assertThat(isOn).isFalse()
        assertThat(mode).isEmpty()
    }

    @Test
    fun initialize_whenNotSensitive_isOff() {
        disableSensitive()
        enableMode()

        initialize(looper, resolver, this::callback)

        assertThat(isOn).isFalse()
        assertThat(mode).isEmpty()
    }

    @Test
    fun enable_whenNotSensitive_isOff() {
        disableSensitive()
        disableMode()

        initialize(looper, resolver, this::callback)

        enableMode()

        assertThat(isOn).isFalse()
        assertThat(mode).isEmpty()
    }

    @Test
    fun initialize_whenSensitive_isOff() {
        enableSensitive()
        disableMode()

        initialize(looper, resolver, this::callback)

        assertThat(isOn).isFalse()
        assertThat(mode).isEmpty()
    }

    @Test
    fun initialize_whenSensitive_isOn() {
        enableSensitive()
        enableMode()

        initialize(looper, resolver, this::callback)

        assertThat(isOn).isTrue()
        assertThat(mode).isEmpty()
    }

    @Test
    fun toggleSensitive_whenEnabled_isOnOffOn() {
        enableSensitive()
        enableMode()

        initialize(looper, resolver, this::callback)

        disableSensitive()
        enableSensitive()

        assertThat(isOn).isTrue()
        assertThat(mode).containsExactly(false, true)
    }

    @Test
    fun toggleEnable_whenSensitive_isOffOnOff() {
        enableSensitive()
        disableMode()

        initialize(looper, resolver, this::callback)

        enableMode()
        disableMode()

        assertThat(isOn).isFalse()
        assertThat(mode).containsExactly(true, false)
    }

    @Test
    fun disable_whenDisabled_discardUpdate() {
        enableSensitive()
        disableMode()

        initialize(looper, resolver, this::callback)

        disableMode()

        assertThat(isOn).isFalse()
        assertThat(mode).isEmpty()
    }

    @Test
    fun enabled_whenEnabled_discardOnChange() {
        enableSensitive()
        enableMode()

        initialize(looper, resolver, this::callback)

        enableMode()

        assertThat(isOn).isTrue()
        assertThat(mode).isEmpty()
    }

    @Test
    fun changeContent_whenDisabled_discard() {
        enableSensitive()
        disableMode()

        initialize(looper, resolver, this::callback)

        disableSensitive()
        enableMode()

        assertThat(isOn).isFalse()
        // As opposed to the bare RadioModeListener, similar consecutive event are discarded
        assertThat(mode).isEmpty()
    }
}
+8 −0
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ import android.os.Looper;
import android.provider.Settings;

import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Assert;
@@ -61,6 +62,13 @@ public class BluetoothAirplaneModeListenerTest {
    @Mock private PackageManager mPackageManager;
    @Mock private Resources mResources;

    static {
        // Required for reading DeviceConfig during BluetoothManagerService static init
        InstrumentationRegistry.getInstrumentation()
                .getUiAutomation()
                .adoptShellPermissionIdentity(android.Manifest.permission.READ_DEVICE_CONFIG);
    }

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
Loading