Fix refresh tile when tile is already listening

Calling refreshTile externally on a tile that is already listening can
have unexpected side effects, for example if the tile is tracking a user
interaction (e.g. turning on bt).

In particular, QSPanelController should not call it for tiles that are already
listening. This is the case for tiles that are also in QQS.

Test: manual, toggle bt ON in QQS and expand quickly.
Test: atest com.android.systemui.qs
Fixes: 225404999
Change-Id: Id0559ad95ecf6a28475abbdd8b989c745badd0b8
This commit is contained in:
Fabian Kozynski
2022-03-24 10:30:27 -04:00
parent 1283caacb6
commit 0ca203c23c
7 changed files with 76 additions and 8 deletions

View File

@@ -39,7 +39,7 @@ import java.util.function.Supplier;
@DependsOn(target = Icon.class) @DependsOn(target = Icon.class)
@DependsOn(target = State.class) @DependsOn(target = State.class)
public interface QSTile { public interface QSTile {
int VERSION = 3; int VERSION = 4;
String getTileSpec(); String getTileSpec();
@@ -114,6 +114,12 @@ public interface QSTile {
return false; return false;
} }
/**
* Return whether the tile is set to its listening state and therefore receiving updates and
* refreshes from controllers
*/
boolean isListening();
@ProvidesInterface(version = Callback.VERSION) @ProvidesInterface(version = Callback.VERSION)
interface Callback { interface Callback {
static final int VERSION = 2; static final int VERSION = 2;

View File

@@ -217,9 +217,14 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
/** */ /** */
public void refreshAllTiles() { public void refreshAllTiles() {
for (QSPanelControllerBase.TileRecord r : mRecords) { for (QSPanelControllerBase.TileRecord r : mRecords) {
if (!r.tile.isListening()) {
// Only refresh tiles that were not already in the listening state. Tiles that are
// already listening is as if they are already expanded (for example, tiles that
// are both in QQS and QS).
r.tile.refreshState(); r.tile.refreshState();
} }
} }
}
private void addTile(final QSTile tile, boolean collapsedView) { private void addTile(final QSTile tile, boolean collapsedView) {
final TileRecord r = final TileRecord r =

View File

@@ -331,6 +331,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
refreshState(null); refreshState(null);
} }
@Override
public final boolean isListening() {
return getLifecycle().getCurrentState().isAtLeast(RESUMED);
}
protected final void refreshState(@Nullable Object arg) { protected final void refreshState(@Nullable Object arg) {
mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
} }
@@ -416,7 +421,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
@Nullable @Nullable
public abstract Intent getLongClickIntent(); public abstract Intent getLongClickIntent();
protected void handleRefreshState(@Nullable Object arg) { protected final void handleRefreshState(@Nullable Object arg) {
handleUpdateState(mTmpState, arg); handleUpdateState(mTmpState, arg);
boolean changed = mTmpState.copyTo(mState); boolean changed = mTmpState.copyTo(mState);
if (mReadyState == READY_STATE_READYING) { if (mReadyState == READY_STATE_READYING) {

View File

@@ -46,6 +46,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost; import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.plugins.qs.QSTileView;
import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.logging.QSLogger;
@@ -62,6 +63,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Collections; import java.util.Collections;
import java.util.List;
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@RunWithLooper @RunWithLooper
@@ -89,6 +91,8 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@Mock @Mock
QSTileImpl mQSTile; QSTileImpl mQSTile;
@Mock @Mock
QSTile mOtherTile;
@Mock
QSTileView mQSTileView; QSTileView mQSTileView;
@Mock @Mock
PagedTileLayout mPagedTileLayout; PagedTileLayout mPagedTileLayout;
@@ -280,4 +284,17 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
assertThat(mController.shouldUseHorizontalLayout()).isFalse(); assertThat(mController.shouldUseHorizontalLayout()).isFalse();
verify(mHorizontalLayoutListener, times(2)).run(); verify(mHorizontalLayoutListener, times(2)).run();
} }
@Test
public void testRefreshAllTilesDoesntRefreshListeningTiles() {
when(mQSTileHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
mController.setTiles();
when(mQSTile.isListening()).thenReturn(false);
when(mOtherTile.isListening()).thenReturn(true);
mController.refreshAllTiles();
verify(mQSTile).refreshState();
verify(mOtherTile, never()).refreshState();
}
} }

View File

@@ -4,13 +4,13 @@ import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import com.android.internal.logging.MetricsLogger import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.media.MediaHost import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.customize.QSCustomizerController import com.android.systemui.qs.customize.QSCustomizerController
import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.settings.brightness.BrightnessController import com.android.systemui.settings.brightness.BrightnessController
@@ -21,11 +21,12 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.any import org.mockito.Mockito.any
import org.mockito.Mockito.reset import org.mockito.Mockito.reset
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@@ -49,6 +50,8 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var falsingManager: FalsingManager @Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var featureFlags: FeatureFlags @Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var mediaHost: MediaHost @Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var tile: QSTile
@Mock private lateinit var otherTile: QSTile
private lateinit var controller: QSPanelController private lateinit var controller: QSPanelController
@@ -93,8 +96,17 @@ class QSPanelControllerTest : SysuiTestCase() {
verify(mediaHost).expansion = MediaHostState.EXPANDED verify(mediaHost).expansion = MediaHostState.EXPANDED
} }
private fun setSplitShadeEnabled(enabled: Boolean) { @Test
mContext.orCreateTestableResources fun testSetListeningDoesntRefreshListeningTiles() {
.addOverride(R.bool.config_use_split_notification_shade, enabled) whenever(qsTileHost.getTiles()).thenReturn(listOf(tile, otherTile))
controller.setTiles()
whenever(tile.isListening()).thenReturn(false)
whenever(otherTile.isListening()).thenReturn(true)
whenever(qsPanel.isListening).thenReturn(true)
controller.setListening(true, true)
verify(tile).refreshState()
verify(otherTile, Mockito.never()).refreshState()
} }
} }

View File

@@ -372,6 +372,11 @@ public class TileQueryHelperTest extends SysuiTestCase {
} }
} }
@Override
public boolean isListening() {
return mListening;
}
@Override @Override
public CharSequence getTileLabel() { public CharSequence getTileLabel() {
return mSpec; return mSpec;

View File

@@ -361,6 +361,24 @@ public class QSTileImplTest extends SysuiTestCase {
assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, captor.getValue().getAction()); assertEquals(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS, captor.getValue().getAction());
} }
@Test
public void testIsListening() {
Object o = new Object();
mTile.setListening(o, true);
mTestableLooper.processAllMessages();
assertTrue(mTile.isListening());
mTile.setListening(o, false);
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
mTile.setListening(o, true);
mTile.destroy();
mTestableLooper.processAllMessages();
assertFalse(mTile.isListening());
}
private void assertEvent(UiEventLogger.UiEventEnum eventType, private void assertEvent(UiEventLogger.UiEventEnum eventType,
UiEventLoggerFake.FakeUiEvent fakeEvent) { UiEventLoggerFake.FakeUiEvent fakeEvent) {
assertEquals(eventType.getId(), fakeEvent.eventId); assertEquals(eventType.getId(), fakeEvent.eventId);