Merge "Do not use mStaleListener if tile is listening" into tm-dev
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index f78046d..57193e7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -33,7 +33,7 @@
 
     String ACTION = "com.android.systemui.action.PLUGIN_QS";
 
-    int VERSION = 14;
+    int VERSION = 15;
 
     String TAG = "QS";
 
@@ -50,6 +50,14 @@
     void setOverscrolling(boolean overscrolling);
     void setExpanded(boolean qsExpanded);
     void setListening(boolean listening);
+
+    /**
+     * Set whether QQS/QS is visible or not.
+     *
+     * This is different from setExpanded, as it will be true when QQS is visible. In particular,
+     * it should be false when device is locked and only notifications (in lockscreen) are visible.
+     */
+    void setQsVisible(boolean qsVisible);
     boolean isShowingDetail();
     void closeDetail();
     void animateHeaderSlidingOut();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index f87cb29..a92c99e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -72,6 +72,7 @@
     private static final boolean DEBUG = false;
     private static final String EXTRA_EXPANDED = "expanded";
     private static final String EXTRA_LISTENING = "listening";
+    private static final String EXTRA_VISIBLE = "visible";
 
     private final Rect mQsBounds = new Rect();
     private final StatusBarStateController mStatusBarStateController;
@@ -148,6 +149,10 @@
 
     private boolean mOverScrolling;
 
+    // Whether QQS or QS is visible. When in lockscreen, this is true if and only if QQS or QS is
+    // visible;
+    private boolean mQsVisible;
+
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
             QSTileHost qsTileHost,
@@ -224,6 +229,7 @@
         mQSCustomizerController.init();
         mQSCustomizerController.setQs(this);
         if (savedInstanceState != null) {
+            setQsVisible(savedInstanceState.getBoolean(EXTRA_VISIBLE));
             setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
             setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
             setEditLocation(view);
@@ -285,6 +291,7 @@
         super.onSaveInstanceState(outState);
         outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
         outState.putBoolean(EXTRA_LISTENING, mListening);
+        outState.putBoolean(EXTRA_VISIBLE, mQsVisible);
         if (mQSCustomizerController != null) {
             mQSCustomizerController.saveInstanceState(outState);
         }
@@ -303,6 +310,11 @@
         return mQsExpanded;
     }
 
+    @VisibleForTesting
+    boolean isQsVisible() {
+        return mQsVisible;
+    }
+
     @Override
     public View getHeader() {
         return mHeader;
@@ -458,7 +470,7 @@
     public void setExpanded(boolean expanded) {
         if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
         mQsExpanded = expanded;
-        mQSPanelController.setListening(mListening, mQsExpanded);
+        updateQsPanelControllerListening();
         updateQsState();
     }
 
@@ -486,9 +498,20 @@
     public void setListening(boolean listening) {
         if (DEBUG) Log.d(TAG, "setListening " + listening);
         mListening = listening;
-        mQSContainerImplController.setListening(listening);
-        mQSFooterActionController.setListening(listening);
-        mQSPanelController.setListening(mListening, mQsExpanded);
+        mQSContainerImplController.setListening(listening && mQsVisible);
+        mQSFooterActionController.setListening(listening && mQsVisible);
+        updateQsPanelControllerListening();
+    }
+
+    private void updateQsPanelControllerListening() {
+        mQSPanelController.setListening(mListening && mQsVisible, mQsExpanded);
+    }
+
+    @Override
+    public void setQsVisible(boolean visible) {
+        if (DEBUG) Log.d(TAG, "setQsVisible " + visible);
+        mQsVisible = visible;
+        setListening(mListening);
     }
 
     @Override
@@ -836,6 +859,7 @@
         indentingPw.println("mHeaderAnimating: " + mHeaderAnimating);
         indentingPw.println("mStackScrollerOverscrolling: " + mStackScrollerOverscrolling);
         indentingPw.println("mListening: " + mListening);
+        indentingPw.println("mQsVisible: " + mQsVisible);
         indentingPw.println("mLayoutDirection: " + mLayoutDirection);
         indentingPw.println("mLastQSExpansion: " + mLastQSExpansion);
         indentingPw.println("mLastPanelFraction: " + mLastPanelFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index 851307a..918c6be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -161,9 +161,6 @@
     /** */
     public void setListening(boolean listening, boolean expanded) {
         setListening(listening && expanded);
-        if (mView.isListening()) {
-            refreshAllTiles();
-        }
 
         // Set the listening as soon as the QS fragment starts listening regardless of the
         //expansion, so it will update the current brightness before the slider is visible.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 93f245b..ec61ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -308,12 +308,17 @@
     }
 
     void setListening(boolean listening) {
+        if (mView.isListening() == listening) return;
         mView.setListening(listening);
 
         if (mView.getTileLayout() != null) {
             mQSLogger.logAllTilesChangeListening(listening, mView.getDumpableTag(), mCachedSpecs);
             mView.getTileLayout().setListening(listening, mUiEventLogger);
         }
+
+        if (mView.isListening()) {
+            refreshAllTiles();
+        }
     }
 
     boolean switchTileLayout(boolean force) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index c6ebd73..833573d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -104,15 +104,6 @@
         mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
     }
 
-    @Override
-    void setListening(boolean listening) {
-        super.setListening(listening);
-    }
-
-    public boolean isListening() {
-        return mView.isListening();
-    }
-
     private void setMaxTiles(int parseNumTiles) {
         mView.setMaxTiles(parseNumTiles);
         setTiles();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 2c6972a..ec0d081 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -175,9 +175,6 @@
         mListening = listening;
 
         mQuickQSPanelController.setListening(listening);
-        if (mQuickQSPanelController.isListening()) {
-            mQuickQSPanelController.refreshAllTiles();
-        }
 
         if (mQuickQSPanelController.switchTileLayout(false)) {
             mView.updateResources();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index a47bd5c..740e12a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -233,7 +233,13 @@
 
     @VisibleForTesting
     protected void handleStale() {
-        setListening(mStaleListener, true);
+        if (!mListeners.isEmpty()) {
+            // If the tile is already listening (it's been a long time since it refreshed), just
+            // force a refresh. Don't add the staleListener because there's already a listener there
+            refreshState();
+        } else {
+            setListening(mStaleListener, true);
+        }
     }
 
     public String getTileSpec() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 960678f..2dbbb145 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -2571,6 +2571,7 @@
             mQsClipTop = (int) (top - currentTranslation - mQsFrame.getTop());
             mQsClipBottom = (int) (bottom - currentTranslation - mQsFrame.getTop());
             mQsVisible = qsVisible;
+            mQs.setQsVisible(mQsVisible);
             mQs.setFancyClipping(
                     mQsClipTop,
                     mQsClipBottom,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index a518b80..664af75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -20,6 +20,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -161,6 +163,7 @@
         QSFragment qs = (QSFragment) mFragment;
         qs.setListening(true);
         qs.setExpanded(true);
+        qs.setQsVisible(true);
         processAllMessages();
         recreateFragment();
         processAllMessages();
@@ -169,6 +172,7 @@
         qs = (QSFragment) mFragment;
         assertTrue(qs.isListening());
         assertTrue(qs.isExpanded());
+        assertTrue(qs.isQsVisible());
     }
 
     @Test
@@ -333,6 +337,54 @@
         assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
     }
 
+    @Test
+    public void setListeningFalse_notVisible() {
+        QSFragment fragment = resumeAndGetFragment();
+        fragment.setQsVisible(false);
+        clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
+
+        fragment.setListening(false);
+        verify(mQSContainerImplController).setListening(false);
+        verify(mQSFooterActionController).setListening(false);
+        verify(mQSPanelController).setListening(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void setListeningTrue_notVisible() {
+        QSFragment fragment = resumeAndGetFragment();
+        fragment.setQsVisible(false);
+        clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
+
+        fragment.setListening(true);
+        verify(mQSContainerImplController).setListening(false);
+        verify(mQSFooterActionController).setListening(false);
+        verify(mQSPanelController).setListening(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void setListeningFalse_visible() {
+        QSFragment fragment = resumeAndGetFragment();
+        fragment.setQsVisible(true);
+        clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
+
+        fragment.setListening(false);
+        verify(mQSContainerImplController).setListening(false);
+        verify(mQSFooterActionController).setListening(false);
+        verify(mQSPanelController).setListening(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void setListeningTrue_visible() {
+        QSFragment fragment = resumeAndGetFragment();
+        fragment.setQsVisible(true);
+        clearInvocations(mQSContainerImplController, mQSPanelController, mQSFooterActionController);
+
+        fragment.setListening(true);
+        verify(mQSContainerImplController).setListening(true);
+        verify(mQSFooterActionController).setListening(true);
+        verify(mQSPanelController).setListening(eq(true), anyBoolean());
+    }
+
     @Override
     protected Fragment instantiate(Context context, String className, Bundle arguments) {
         MockitoAnnotations.initMocks(this);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 4dbf3d8..c127a6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -137,6 +137,10 @@
         when(mMediaHost.getDisappearParameters()).thenReturn(new DisappearParameters());
         when(mQSPanel.getResources()).thenReturn(mResources);
         when(mResources.getConfiguration()).thenReturn(mConfiguration);
+        doAnswer(invocation -> {
+            when(mQSPanel.isListening()).thenReturn(invocation.getArgument(0));
+            return null;
+        }).when(mQSPanel).setListening(anyBoolean());
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
                 mQSCustomizerController, mMediaHost,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 69d3f8b..c0944ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -21,6 +21,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
@@ -62,6 +63,9 @@
         whenever(brightnessControllerFactory.create(any())).thenReturn(brightnessController)
         whenever(qsPanel.resources).thenReturn(mContext.orCreateTestableResources.resources)
         whenever(statusBarKeyguardViewManager.isBouncerInTransit()).thenReturn(false)
+        whenever(qsPanel.setListening(anyBoolean())).then {
+            whenever(qsPanel.isListening).thenReturn(it.getArgument(0))
+        }
 
         controller = QSPanelController(
             qsPanel,
@@ -100,7 +104,6 @@
         controller.setTiles()
         whenever(tile.isListening()).thenReturn(false)
         whenever(otherTile.isListening()).thenReturn(true)
-        whenever(qsPanel.isListening).thenReturn(true)
 
         controller.setListening(true, true)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index c31a971..5336ef0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -379,6 +379,34 @@
         assertFalse(mTile.isListening());
     }
 
+    @Test
+    public void testStaleTriggeredWhileListening() throws Exception {
+        Object o = new Object();
+        mTile.clearRefreshes();
+
+        mTile.setListening(o, true); // +1 refresh
+        mTestableLooper.processAllMessages();
+
+        mTestableLooper.runWithLooper(() -> mTile.handleStale()); // +1 refresh
+        mTestableLooper.processAllMessages();
+
+        mTile.setListening(o, false);
+        mTestableLooper.processAllMessages();
+        assertFalse(mTile.isListening());
+        assertThat(mTile.mRefreshes).isEqualTo(2);
+    }
+
+    @Test
+    public void testStaleTriggeredWhileNotListening() throws Exception {
+        mTile.clearRefreshes();
+
+        mTestableLooper.runWithLooper(() -> mTile.handleStale()); // +1 refresh
+        mTestableLooper.processAllMessages();
+
+        assertFalse(mTile.isListening());
+        assertThat(mTile.mRefreshes).isEqualTo(1);
+    }
+
     private void assertEvent(UiEventLogger.UiEventEnum eventType,
             UiEventLoggerFake.FakeUiEvent fakeEvent) {
         assertEquals(eventType.getId(), fakeEvent.eventId);
@@ -418,9 +446,9 @@
             return mInvalid;
         }
     }
-
     private static class TileImpl extends QSTileImpl<QSTile.BooleanState> {
         boolean mClicked;
+        int mRefreshes = 0;
 
         protected TileImpl(
                 QSHost host,
@@ -453,6 +481,11 @@
 
         @Override
         protected void handleUpdateState(BooleanState state, Object arg) {
+            mRefreshes++;
+        }
+
+        void clearRefreshes() {
+            mRefreshes = 0;
         }
 
         @Override