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

Commit d7f79f7d authored by Chelsea Hao's avatar Chelsea Hao Committed by Android (Google) Code Review
Browse files

Merge changes from topic "newapi" into main

* changes:
  Use autoOn new api.
  Add auto on broadcast handling.
parents cf6b89f8 bda9a719
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -163,6 +163,16 @@ public interface BluetoothCallback {
    default void onAclConnectionStateChanged(
            @NonNull CachedBluetoothDevice cachedDevice, int state) {}

    /**
     * Called when the Auto-on state is changed for any user. Listens to intent
     * {@link android.bluetooth.BluetoothAdapter#ACTION_AUTO_ON_STATE_CHANGED }
     *
     * @param state        the Auto-on state, the possible values are:
     *                     {@link android.bluetooth.BluetoothAdapter#AUTO_ON_STATE_ENABLED},
     *                     {@link android.bluetooth.BluetoothAdapter#AUTO_ON_STATE_DISABLED}
     */
    default void onAutoOnStateChanged(int state) {}

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "STATE_" }, value = {
            STATE_DISCONNECTED,
+19 −0
Original line number Diff line number Diff line
@@ -133,6 +133,8 @@ public class BluetoothEventManager {
        addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
        addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());

        addHandler(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED, new AutoOnStateChangedHandler());

        registerAdapterIntentReceiver();
    }

@@ -552,4 +554,21 @@ public class BluetoothEventManager {
            dispatchAudioModeChanged();
        }
    }

    private class AutoOnStateChangedHandler implements Handler {

        @Override
        public void onReceive(Context context, Intent intent, BluetoothDevice device) {
            String action = intent.getAction();
            if (action == null) {
                Log.w(TAG, "AutoOnStateChangedHandler() action is null");
                return;
            }
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_AUTO_ON_STATE,
                    BluetoothAdapter.ERROR);
            for (BluetoothCallback callback : mCallbacks) {
                callback.onAutoOnStateChanged(state);
            }
        }
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -489,4 +490,17 @@ public class BluetoothEventManagerTest {
        verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
                eq(R.string.bluetooth_pairing_pin_error_message));
    }

    /**
     * Intent ACTION_AUTO_ON_STATE_CHANGED should dispatch to callback.
     */
    @Test
    public void intentWithExtraState_autoOnStateChangedShouldDispatchToRegisterCallback() {
        mBluetoothEventManager.registerCallback(mBluetoothCallback);
        mIntent = new Intent(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED);

        mContext.sendBroadcast(mIntent);

        verify(mBluetoothCallback).onAutoOnStateChanged(anyInt());
    }
}
+5 −14
Original line number Diff line number Diff line
@@ -19,8 +19,6 @@ package com.android.systemui.qs.tiles.dialog.bluetooth
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map

/** Interactor class responsible for interacting with the Bluetooth Auto-On feature. */
@SysUISingleton
@@ -30,14 +28,10 @@ constructor(
    private val bluetoothAutoOnRepository: BluetoothAutoOnRepository,
) {

    val isEnabled = bluetoothAutoOnRepository.isAutoOn.map { it == ENABLED }.distinctUntilChanged()
    val isEnabled = bluetoothAutoOnRepository.isAutoOn

    /**
     * Checks if the auto on value is present in the repository.
     *
     * @return `true` if a value is present (i.e, the feature is enabled by the Bluetooth server).
     */
    suspend fun isValuePresent(): Boolean = bluetoothAutoOnRepository.isValuePresent()
    /** Checks if the auto on feature is supported. */
    suspend fun isAutoOnSupported(): Boolean = bluetoothAutoOnRepository.isAutoOnSupported()

    /**
     * Sets enabled or disabled based on the provided value.
@@ -45,17 +39,14 @@ constructor(
     * @param value `true` to enable the feature, `false` to disable it.
     */
    suspend fun setEnabled(value: Boolean) {
        if (!isValuePresent()) {
        if (!isAutoOnSupported()) {
            Log.e(TAG, "Trying to set toggle value while feature not available.")
        } else {
            val newValue = if (value) ENABLED else DISABLED
            bluetoothAutoOnRepository.setAutoOn(newValue)
            bluetoothAutoOnRepository.setAutoOn(value)
        }
    }

    companion object {
        private const val TAG = "BluetoothAutoOnInteractor"
        const val DISABLED = 0
        const val ENABLED = 1
    }
}
+76 −49
Original line number Diff line number Diff line
@@ -16,22 +16,23 @@

package com.android.systemui.qs.tiles.dialog.bluetooth

import android.bluetooth.BluetoothAdapter
import android.util.Log
import com.android.settingslib.bluetooth.BluetoothCallback
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
@@ -44,61 +45,87 @@ import kotlinx.coroutines.withContext
class BluetoothAutoOnRepository
@Inject
constructor(
    private val secureSettings: SecureSettings,
    private val userRepository: UserRepository,
    localBluetoothManager: LocalBluetoothManager?,
    private val bluetoothAdapter: BluetoothAdapter?,
    @Application private val coroutineScope: CoroutineScope,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) {

    // Flow representing the auto on setting value for the current user
    @OptIn(ExperimentalCoroutinesApi::class)
    internal val isAutoOn: StateFlow<Int> =
        userRepository.selectedUserInfo
            .flatMapLatest { userInfo ->
                secureSettings
                    .observerFlow(userInfo.id, SETTING_NAME)
                    .onStart { emit(Unit) }
                    .map { secureSettings.getIntForUser(SETTING_NAME, UNSET, userInfo.id) }
    // Flow representing the auto on state for the current user
    internal val isAutoOn: Flow<Boolean> =
        localBluetoothManager?.eventManager?.let { eventManager ->
            conflatedCallbackFlow {
                    val listener =
                        object : BluetoothCallback {
                            override fun onAutoOnStateChanged(autoOnState: Int) {
                                super.onAutoOnStateChanged(autoOnState)
                                if (
                                    autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED ||
                                        autoOnState == BluetoothAdapter.AUTO_ON_STATE_DISABLED
                                ) {
                                    trySendWithFailureLogging(
                                        autoOnState == BluetoothAdapter.AUTO_ON_STATE_ENABLED,
                                        TAG,
                                        "onAutoOnStateChanged"
                                    )
                                }
                            }
                        }
            .distinctUntilChanged()
                    eventManager.registerCallback(listener)
                    awaitClose { eventManager.unregisterCallback(listener) }
                }
                .onStart { emit(isAutoOnEnabled()) }
                .flowOn(backgroundDispatcher)
                .stateIn(
                    coroutineScope,
                    SharingStarted.WhileSubscribed(replayExpirationMillis = 0),
                UNSET
                    initialValue = false
                )
        }
            ?: flowOf(false)

    /**
     * Checks if the auto on setting value is ever set for the current user.
     * Checks if the auto on feature is supported for the current user.
     *
     * @return `true` if the setting value is not UNSET, `false` otherwise.
     * @throws Exception if an error occurs while checking auto-on support.
     */
    suspend fun isValuePresent(): Boolean =
    suspend fun isAutoOnSupported(): Boolean =
        withContext(backgroundDispatcher) {
            secureSettings.getIntForUser(
                SETTING_NAME,
                UNSET,
                userRepository.getSelectedUserInfo().id
            ) != UNSET
            try {
                bluetoothAdapter?.isAutoOnSupported ?: false
            } catch (e: Exception) {
                // Server could throw TimeoutException, InterruptedException or ExecutionException
                Log.e(TAG, "Error calling isAutoOnSupported", e)
                false
            }
        }

    /**
     * Sets the Bluetooth Auto-On setting value for the current user.
     *
     * @param value The new setting value to be applied.
     */
    suspend fun setAutoOn(value: Int) {
    /** Sets the Bluetooth Auto-On for the current user. */
    suspend fun setAutoOn(value: Boolean) {
        withContext(backgroundDispatcher) {
            secureSettings.putIntForUser(
                SETTING_NAME,
                value,
                userRepository.getSelectedUserInfo().id
            )
            try {
                bluetoothAdapter?.setAutoOnEnabled(value)
            } catch (e: Exception) {
                // Server could throw IllegalStateException, TimeoutException, InterruptedException
                // or ExecutionException
                Log.e(TAG, "Error calling setAutoOnEnabled", e)
            }
        }
    }

    private suspend fun isAutoOnEnabled() =
        withContext(backgroundDispatcher) {
            try {
                bluetoothAdapter?.isAutoOnEnabled ?: false
            } catch (e: Exception) {
                // Server could throw IllegalStateException, TimeoutException, InterruptedException
                // or ExecutionException
                Log.e(TAG, "Error calling isAutoOnEnabled", e)
                false
            }
        }

    companion object {
        const val SETTING_NAME = "bluetooth_automatic_turn_on"
        const val UNSET = -1
    private companion object {
        const val TAG = "BluetoothAutoOnRepository"
    }
}
Loading