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

Commit c26a815f authored by William Escande's avatar William Escande
Browse files

SystemServer: AutoOn: Hidden api listener

Since the auto on feature may be used before the release of android V
APIs, we are hadding a hacky way to interact with it

Bug: 323060869
Bug: 316946334
Test: atest ServiceBluetoothRoboTests
Change-Id: I774ad4eece375f48aa77e6113c0d1f2e62643aad
parent 5e29ffab
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -81,6 +81,7 @@ java_defaults {
        "bluetooth-nano-protos",
        "bluetooth-proto-enums-java-gen",
        "bluetooth_flags_java_lib",
        "modules-utils-build_system",
        "modules-utils-shell-command-handler",
    ],

+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ sourceSets.main {
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2")
    implementation(files("../../../../external/kotlinc/lib/kotlin-stdlib.jar"))
    implementation(files("../../../../out/soong/.intermediates/frameworks/libs/modules-utils/java/com/android/modules/utils/build/modules-utils-build_system/android_common_apex33/turbine-combined/modules-utils-build_system.jar"))
    implementation(files("../../../../out/soong/.intermediates/frameworks/libs/modules-utils/java/com/android/modules/utils/modules-utils-shell-command-handler/android_common_apex33/turbine-combined/modules-utils-shell-command-handler.jar"))
    implementation(files("../../../../out/soong/.intermediates/frameworks/libs/modules-utils/java/framework-annotations-lib/android_common/turbine-combined/framework-annotations-lib.jar"))
    implementation(files("../../../../out/soong/.intermediates/packages/modules/Bluetooth/framework/framework-bluetooth-pre-jarjar/android_common/turbine-combined/framework-bluetooth-pre-jarjar.jar"))
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ CLASSPATH+=":$ROOT/out/soong/.intermediates/packages/modules/Bluetooth/service/b
CLASSPATH+=":$ROOT/out/soong/.intermediates/packages/modules/Bluetooth/android/app/bluetooth-proto-enums-java-gen/android_common_apex33/turbine-combined/bluetooth-proto-enums-java-gen.jar"
CLASSPATH+=":$ROOT/out/soong/.intermediates/packages/modules/Bluetooth/flags/bluetooth_flags_java_lib/android_common_apex33/turbine-combined/bluetooth_flags_java_lib.jar"
CLASSPATH+=":$ROOT/out/soong/.intermediates/frameworks/libs/modules-utils/java/com/android/modules/utils/modules-utils-shell-command-handler/android_common_apex33/turbine-combined/modules-utils-shell-command-handler.jar"
CLASSPATH+=":$ROOT/out/soong/.intermediates/frameworks/libs/modules-utils/java/com/android/modules/utils/build/modules-utils-build_system/android_common_apex33/turbine-combined/modules-utils-build_system.jar"

CLASSPATH+=":$ROOT/out/soong/.intermediates/prebuilts/misc/common/androidx-test/androidx.test.core/android_common/combined/androidx.test.core.jar"
CLASSPATH+=":$ROOT/out/soong/.intermediates/external/truth/truth/android_common/turbine-combined/truth.jar"
+87 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.ContentObserver
import android.os.Handler
import android.os.Looper
import android.provider.Settings
@@ -125,6 +126,16 @@ public fun setUserEnabled(
    resetAutoOnTimerForUser(looper, context, state, callback_on)
}

// Listener is needed because code should be actionable prior to V API release
public fun registerHiddenApiListener(
    looper: Looper,
    context: Context,
    state: BluetoothAdapterState,
    callback_on: () -> Unit
) {
    HiddenApiListener.registerUser(looper, context, state, callback_on)
}

////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////// PRIVATE METHODS /////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -259,3 +270,79 @@ private fun isFeatureSupportedForUser(resolver: ContentResolver): Boolean {
private fun setFeatureEnabledForUserUnchecked(resolver: ContentResolver, status: Boolean) {
    Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, if (status) 1 else 0)
}

// Listener is needed because code should be actionable prior to V API release
@VisibleForTesting
internal class HiddenApiListener
private constructor(
    looper: Looper,
    private val context: Context,
    state: BluetoothAdapterState,
    callback_on: () -> Unit
) {
    companion object {
        @VisibleForTesting internal var listener: HiddenApiListener? = null

        fun registerUser(
            looper: Looper,
            context: Context,
            state: BluetoothAdapterState,
            callback_on: () -> Unit
        ) {
            // Remove observer on previous user
            listener?.remove()
            listener = HiddenApiListener(looper, context, state, callback_on)
        }
    }

    private val handler = Handler(looper)

    private val observer =
        object : ContentObserver(handler) {
            override fun onChange(selfChange: Boolean) {
                var previousState = featureState
                var newState =
                    Settings.Secure.getInt(context.contentResolver, USER_SETTINGS_KEY, -1)
                featureState = newState

                if (previousState == newState) {
                    Log.d(TAG, "HiddenApi: State is unchanged: ${newState}")
                    return
                }

                if (previousState == -1) {
                    Log.d(TAG, "HiddenApi: Feature default state got setup to ${newState}")
                    return
                }

                Log.d(TAG, "HiddenApi: Feature state change from ${previousState} to ${newState}")

                Counter.logIncrement("bluetooth.value_auto_on_hidden_usage")
                Counter.logIncrement(
                    if (newState == 1) "bluetooth.value_auto_on_enabled"
                    else "bluetooth.value_auto_on_disabled"
                )

                resetAutoOnTimerForUser(looper, context, state, callback_on)
            }
        }

    private var featureState =
        Settings.Secure.getInt(context.contentResolver, USER_SETTINGS_KEY, -1)

    init {
        val notifyForDescendants = false

        context.contentResolver.registerContentObserver(
            Settings.Secure.getUriFor(USER_SETTINGS_KEY),
            notifyForDescendants,
            observer
        )
    }

    @VisibleForTesting
    internal fun remove() {
        context.contentResolver.unregisterContentObserver(observer)
        handler.removeCallbacksAndMessages(null)
    }
}
+107 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.os.Looper
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
import com.android.server.bluetooth.BluetoothAdapterState
import com.android.server.bluetooth.HiddenApiListener
import com.android.server.bluetooth.Log
import com.android.server.bluetooth.Timer
import com.android.server.bluetooth.USER_SETTINGS_KEY
@@ -31,6 +32,7 @@ import com.android.server.bluetooth.isUserEnabled
import com.android.server.bluetooth.isUserSupported
import com.android.server.bluetooth.notifyBluetoothOn
import com.android.server.bluetooth.pause
import com.android.server.bluetooth.registerHiddenApiListener
import com.android.server.bluetooth.resetAutoOnTimerForUser
import com.android.server.bluetooth.satellite.isOn as isSatelliteModeOn
import com.android.server.bluetooth.satellite.test.ModeListenerTest as SatelliteListener
@@ -53,6 +55,8 @@ import org.robolectric.Shadows.shadowOf
@RunWith(RobolectricTestRunner::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class AutoOnFeatureTest {
    private val SETTING_URI = Settings.Secure.getUriFor(USER_SETTINGS_KEY)

    private val looper = Looper.getMainLooper()
    private val state = BluetoothAdapterState()
    private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -74,6 +78,9 @@ class AutoOnFeatureTest {

    @After
    fun tearDown() {
        HiddenApiListener.listener?.let { it.remove() }
        HiddenApiListener.listener = null

        callback_count = 0
        timer?.cancel()
        timer = null
@@ -401,4 +408,104 @@ class AutoOnFeatureTest {
        expect.that(callback_count).isEqualTo(0)
        expectStorageTime()
    }

    @Test
    fun registerHiddenListener_whenNothing_isRegistered() {
        registerHiddenApiListener(looper, context, state, this::callback_on)

        assertThat(HiddenApiListener.listener).isNotNull()
    }

    @Test
    fun unregisterHiddenListener_whenRegistered_isNotRegistered() {
        registerHiddenApiListener(looper, context, state, this::callback_on)

        HiddenApiListener.listener?.let { it.remove() }

        assertThat(shadowOf(resolver).getContentObservers(SETTING_URI).size).isEqualTo(0)
    }

    @Test
    fun registerHiddenListener_whenAlreadyRegistered_isRegisteredOnce() {
        registerHiddenApiListener(looper, context, state, this::callback_on)

        registerHiddenApiListener(looper, context, state, this::callback_on)

        expect.that(shadowOf(resolver).getContentObservers(SETTING_URI).size).isEqualTo(1)
        expect.that(HiddenApiListener.listener).isNotNull()
    }

    @Test
    fun changeSettingsToDisabled_whenHiddenApiIsRegisteredandNotScheduled_isNotSchedule() {
        registerHiddenApiListener(looper, context, state, this::callback_on)

        disableUserSettings()

        expect.that(timer).isNull()
        expect.that(callback_count).isEqualTo(0)
        expectNoStorageTime()
    }

    @Test
    fun changeSettingsToDisabled_whenHiddenApiIsRegisteredandScheduled_isNotSchedule() {
        setupTimer()
        registerHiddenApiListener(looper, context, state, this::callback_on)

        disableUserSettings()

        expect.that(timer).isNull()
        expect.that(callback_count).isEqualTo(0)
        expectNoStorageTime()
    }

    @Test
    fun changeSettingsToEnabled_whenHiddenApiIsRegisteredandNotScheduled_isSchedule() {
        disableUserSettings()
        registerHiddenApiListener(looper, context, state, this::callback_on)

        enableUserSettings()

        expect.that(timer).isNotNull()
        expect.that(callback_count).isEqualTo(0)
        expectStorageTime()
    }

    @Test
    fun setSettingsToSameValue_whenHiddenApiIsRegisteredandNotScheduled_isNotSchedule() {
        restoreSettings()
        registerHiddenApiListener(looper, context, state, this::callback_on)

        Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, -1)
        shadowOf(looper).idle()

        expect.that(timer).isNull()
        expect.that(callback_count).isEqualTo(0)
        expectNoStorageTime()
    }

    @Test
    fun setSettingsToEnabled_whenHiddenApiIsRegisteredandNotSupported_isNotSchedule() {
        restoreSettings()
        registerHiddenApiListener(looper, context, state, this::callback_on)

        enableUserSettings()

        expect.that(timer).isNull()
        expect.that(callback_count).isEqualTo(0)
        expectNoStorageTime()
    }

    @Test
    fun setSettingsToDisable_whenHiddenApiIsRegisteredandNotSupported_isNotSchedule() {
        // Current design will set the feature to enabled, but there is no reason to not support
        // having a default value to disabled
        restoreSettings()
        registerHiddenApiListener(looper, context, state, this::callback_on)

        disableUserSettings()

        expect.that(timer).isNull()
        expect.that(callback_count).isEqualTo(0)
        expectNoStorageTime()
    }
}
Loading