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

Commit b2afdd7d authored by Alex Shabalin's avatar Alex Shabalin
Browse files

Clear state override when the connection state for it changes.

The SuggestedDeviceManager didn't clear the `suggestedStateOverride`
upon a successful connection in the following scenario:

- `suggestedStateOverride` for device A is set with `STATE_CONNECTING`
- Device B is set as a new suggestion.
- A state for device A changes to `STATE_CONNECTED`

Bug: the connection state check that decides whether to clear
`suggestedStateOverride` was performed on a new suggestion (B) instead
of the suggested state override device itself (A).

Fix: 444612033
Test: atest SuggestedDeviceManagerTest
Test: On a physical device
Flag: com.android.systemui.enable_suggested_device_ui
Change-Id: If1d355f76b375207b1251cf70a05d68da5a3a4e6
parent ec8289b6
Loading
Loading
Loading
Loading
+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 }
+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))