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

Commit dc2b90cd authored by Alexandr Shabalin's avatar Alexandr Shabalin Committed by Android (Google) Code Review
Browse files

Merge changes I7cea195e,If1d355f7 into main

* changes:
  Don't stop scanning until connection is established or failed.
  Clear state override when the connection state for it changes.
parents e9bdfc49 ae9b8951
Loading
Loading
Loading
Loading
+28 −18
Original line number Diff line number Diff line
@@ -25,6 +25,8 @@ import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
@@ -81,21 +83,31 @@ open class SuggestedDeviceConnectionManager(
    private suspend fun awaitConnect(
        suggestedDeviceState: SuggestedDeviceState,
        routingChangeInfo: RoutingChangeInfo,
    ): Boolean {
    ): Boolean = coroutineScope {
        var scanStarted = false
        try {
            val suggestedRouteId = suggestedDeviceState.suggestedDeviceInfo.routeId
        val deviceFromCache = getDeviceByRouteId(localMediaManager.mediaDevices, suggestedRouteId)
            val deviceFromCache =
                getDeviceByRouteId(localMediaManager.mediaDevices, suggestedRouteId)
            val deviceToConnect =
                deviceFromCache?.also { Log.i(TAG, "Device from cache found.") }
                    ?: run {
                        Log.i(TAG, "Scanning for device.")
                    awaitScanForDevice(suggestedRouteId)
                        // Start listening before starting scan to avoid missing events.
                        val scanResultDeferred = async { awaitScanForDevice(suggestedRouteId) }
                        localMediaManager.startScan()
                        scanStarted = true
                        scanResultDeferred.await()
                    }
            if (deviceToConnect == null) {
                Log.w(TAG, "Failed to find a device to connect to. routeId = $suggestedRouteId")
            return false
                return@coroutineScope false
            }
            Log.i(TAG, "Connecting to device. id = ${deviceToConnect.id}")
        return awaitConnectToDevice(deviceToConnect, routingChangeInfo)
            return@coroutineScope awaitConnectToDevice(deviceToConnect, routingChangeInfo)
        } finally {
            if (scanStarted) localMediaManager.stopScan()
        }
    }

    private suspend fun awaitScanForDevice(suggestedRouteId: String): MediaDevice? {
@@ -111,12 +123,11 @@ open class SuggestedDeviceConnectionManager(
                                    TAG,
                                    "Scan found matched device. routeId = $suggestedRouteId",
                                )
                                continuation.resume(device)
                                if (continuation.isActive) continuation.resume(device)
                            }
                        }
                    }
                    localMediaManager.registerCallback(callback)
                    localMediaManager.startScan()
                }
            } ?: run {
                Log.w(TAG, "Scan timed out. routeId = $suggestedRouteId")
@@ -124,7 +135,6 @@ open class SuggestedDeviceConnectionManager(
            }
        } finally {
            localMediaManager.unregisterCallback(callback)
            localMediaManager.stopScan()
        }
    }

@@ -149,7 +159,7 @@ open class SuggestedDeviceConnectionManager(
                        private fun checkConnectionStatus() {
                            if (localMediaManager.currentConnectedDevice?.id == deviceId) {
                                Log.i(TAG, "Successfully connected to device. id = $deviceId")
                                continuation.resume(true)
                                if (continuation.isActive) continuation.resume(true)
                            }
                        }
                    }
+18 −18
Original line number Diff line number Diff line
@@ -230,7 +230,8 @@ class SuggestedDeviceManager(
    newTopSuggestion: SuggestedDeviceInfo?,
    newMediaDevices: List<MediaDevice>,
  ): Boolean {
    val newSuggestedDeviceState =
    tryClearSuggestedStateOverrideLocked(newMediaDevices)
    val newSuggestedDeviceState = suggestedStateOverride ?:
      calculateNewSuggestedDeviceStateLocked(newTopSuggestion, newMediaDevices)
    if (newSuggestedDeviceState != suggestedDeviceState) {
      suggestedDeviceState = newSuggestedDeviceState
@@ -249,20 +250,16 @@ class SuggestedDeviceManager(
    }

    if (newTopSuggestion == null) {
      return suggestedStateOverride ?: null
      return null
    }

    val newConnectionState =
      getConnectionStateFromMatchedDeviceLocked(newTopSuggestion, newMediaDevices)
    if (shouldClearStateOverride(newConnectionState)) {
      clearSuggestedStateOverrideLocked()
    }

    return if (isConnectedState(newConnectionState)) {
      // Don't display a suggestion if the MediaDevice that matches the suggestion is connected.
      null
    } else {
      suggestedStateOverride ?: SuggestedDeviceState(newTopSuggestion, newConnectionState)
      SuggestedDeviceState(newTopSuggestion, newConnectionState)
    }
  }

@@ -279,14 +276,6 @@ class SuggestedDeviceManager(
    return matchedDevice?.state ?: STATE_DISCONNECTED
  }

  private fun shouldClearStateOverride(
    @MediaDeviceState newConnectionState: Int,
  ): Boolean {
    // Don't clear the state override if a matched device is in DISCONNECTED state. Currently, the
    // DISCONNECTED state can be reported during connection that can lead to UI flicker.
    return newConnectionState != STATE_DISCONNECTED
  }

  private fun isConnectedState(@MediaDeviceState state: Int): Boolean =
    state == STATE_CONNECTED || state == STATE_SELECTED

@@ -309,10 +298,21 @@ class SuggestedDeviceManager(
  }

  @GuardedBy("lock")
  private fun clearSuggestedStateOverrideLocked() {
  private fun tryClearSuggestedStateOverrideLocked(newMediaDevices: List<MediaDevice>) {
    suggestedStateOverride?.let { override ->
      val newConnectionState =
          getConnectionStateFromMatchedDeviceLocked(override.suggestedDeviceInfo, newMediaDevices)
      // Clear the state override unless the matched device state is:
      // - STATE_DISCONNECTED: This state might be reported during the connection process,
      //   potentially causing UI flicker.
      // - Same connection state as in the override: Getting an event with the same device state
      //   should not affect the override timeout.
      if (newConnectionState !in setOf(STATE_DISCONNECTED, override.connectionState)) {
        suggestedStateOverride = null
        handler.removeCallbacks(onSuggestedStateOverrideExpiredRunnable)
      }
    }
  }

  private fun dispatchOnSuggestedDeviceUpdated() {
    val state = synchronized(lock) { suggestedDeviceState }
+2 −1
Original line number Diff line number Diff line
@@ -162,7 +162,6 @@ class SuggestedDeviceConnectionManagerTest {
        emulateOnDeviceListUpdate(mediaDevices)
        runCurrent()

        verify(localMediaManager).stopScan()
        verify(localMediaManager).connectDevice(mediaDevice1, ROUTING_CHANGE_INFO)
        assertThat(deviceCallbacks).hasSize(1) // Callback for connection state change
    }
@@ -230,11 +229,13 @@ class SuggestedDeviceConnectionManagerTest {
        runCurrent()

        verify(localMediaManager).connectDevice(mediaDevice1, ROUTING_CHANGE_INFO)
        verify(localMediaManager, never()).stopScan() // don't stop scan until connected.
        localMediaManager.stub { on { currentConnectedDevice } doReturn mediaDevice1 }
        emulateOnSelectedDeviceStateChanged(mediaDevice1, STATE_CONNECTED)
        runCurrent()

        verify(callback).invoke(suggestedDeviceState, true)
        verify(localMediaManager).stopScan()
        assertThat(deviceCallbacks).hasSize(0)
    }

+16 −20
Original line number Diff line number Diff line
@@ -244,24 +244,9 @@ class SuggestedDeviceManagerTest {

  @Test
  fun onDeviceSuggestionsUpdated_hasStateOverrideAndNewSuggestionDifferent_keepsOverriddenState() {
    onDeviceSuggestionsUpdated_hasStateOverride_keepsOverriddenState(
      initialSuggestedDeviceInfo = suggestedDeviceInfo1,
      updatedSuggestedDeviceInfo = suggestedDeviceInfo2,
    )
  }
    val initialSuggestedDeviceInfo = suggestedDeviceInfo1
    val updatedSuggestedDeviceInfo = suggestedDeviceInfo2

  @Test
  fun onDeviceSuggestionsUpdated_hasStateOverrideAndNewSuggestionNull_keepsOverriddenState() {
    onDeviceSuggestionsUpdated_hasStateOverride_keepsOverriddenState(
      initialSuggestedDeviceInfo = suggestedDeviceInfo1,
      updatedSuggestedDeviceInfo = null,
    )
  }

  fun onDeviceSuggestionsUpdated_hasStateOverride_keepsOverriddenState(
    initialSuggestedDeviceInfo: SuggestedDeviceInfo,
    updatedSuggestedDeviceInfo: SuggestedDeviceInfo?,
  ) {
    val deviceCallback = addListenerAndCaptureCallback(listener)
    deviceCallback.onDeviceListUpdate(listOf(mediaDevice1, mediaDevice2))

@@ -294,7 +279,8 @@ class SuggestedDeviceManagerTest {
      initialSuggestedDeviceState,
      false,
    )
    val failedSuggestedState = initialSuggestedDeviceState.copy(connectionState = STATE_CONNECTING_FAILED)
    val failedSuggestedState =
      initialSuggestedDeviceState.copy(connectionState = STATE_CONNECTING_FAILED)
    verify(listener).onSuggestedDeviceStateUpdated(failedSuggestedState)
    clearInvocations(listener)

@@ -340,7 +326,7 @@ class SuggestedDeviceManagerTest {
  }

  @Test
  fun onDeviceListUpdate_fromConnectingOverrideToDisconnected_noDispatch() {
  fun onDeviceListUpdate_fromConnectingOverrideToSameStateOrDisconnected_noDispatch() {
    val deviceCallback = addListenerAndCaptureCallback(listener)

    deviceCallback.onDeviceListUpdate(listOf(mediaDevice1))
@@ -355,10 +341,15 @@ class SuggestedDeviceManagerTest {
    verify(listener)
      .onSuggestedDeviceStateUpdated(SuggestedDeviceState(suggestedDeviceInfo1, STATE_CONNECTING))

    // If the matched device state is the same, override is not cleared.
    mediaDevice1.stub { on { state } doReturn STATE_CONNECTING }
    deviceCallback.onDeviceListUpdate(listOf(mediaDevice1))

    // If the matched device state is STATE_DISCONNECTED, override is not cleared.
    mediaDevice1.stub { on { state } doReturn STATE_DISCONNECTED }
    deviceCallback.onDeviceListUpdate(listOf(mediaDevice1))

    // STATE_DISCONNECTED is ignored.
    // Override was not cleared, therefore no callbacks are dispatched.
    verifyNoMoreInteractions(listener)
  }

@@ -378,6 +369,11 @@ class SuggestedDeviceManagerTest {

    verify(listener)
      .onSuggestedDeviceStateUpdated(SuggestedDeviceState(suggestedDeviceInfo1, STATE_CONNECTING))
    clearInvocations(listener)

    // Changing suggestion doesn't affect override.
    deviceCallback.onDeviceSuggestionsUpdated(listOf(null))
    verify(listener, never()).onSuggestedDeviceStateUpdated(anyOrNull())

    mediaDevice1.stub { on { state } doReturn STATE_CONNECTED }
    deviceCallback.onDeviceListUpdate(listOf(mediaDevice1))