Merge "Do not animate QS state changes when not in view."
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java
index c268d32..0cdb509 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSIconView.java
@@ -29,7 +29,7 @@
         super(context);
     }
 
-    public abstract void setIcon(State state);
+    public abstract void setIcon(State state, boolean allowAnimations);
     public abstract void disableAnimation();
     public abstract View getIconView();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
index e7eefe8..376e6ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/CellTileView.java
@@ -43,9 +43,9 @@
                 R.dimen.qs_tile_icon_size));
     }
 
-    protected void updateIcon(ImageView iv, State state) {
+    protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
         if (!(state.icon instanceof SignalIcon)) {
-            super.updateIcon(iv, state);
+            super.updateIcon(iv, state, allowAnimations);
             return;
         } else if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
             mSignalDrawable.setLevel(((SignalIcon) state.icon).getState());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
index d9583af..ce90fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SignalTileView.java
@@ -18,14 +18,13 @@
 
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.view.View;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.systemui.R;
-import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
+import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
 import com.android.systemui.qs.tileimpl.SlashImageView;
 
@@ -119,9 +118,9 @@
     }
 
     @Override
-    public void setIcon(QSTile.State state) {
+    public void setIcon(State state, boolean allowAnimations) {
         final SignalState s = (SignalState) state;
-        setIcon(mSignal, s);
+        setIcon(mSignal, s, allowAnimations);
 
         if (s.overlayIconId > 0) {
             mOverlay.setVisibility(VISIBLE);
@@ -134,9 +133,9 @@
         } else {
             mSignal.setPaddingRelative(0, 0, 0, 0);
         }
-        final boolean shown = isShown();
-        setVisibility(mIn, shown, s.activityIn);
-        setVisibility(mOut, shown, s.activityOut);
+        final boolean shouldAnimate = allowAnimations && isShown();
+        setVisibility(mIn, shouldAnimate, s.activityIn);
+        setVisibility(mOut, shouldAnimate, s.activityOut);
     }
 
     private void setVisibility(View view, boolean shown, boolean visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e7e756f..9dd5d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -84,16 +84,15 @@
         layout(mIcon, iconLeft, top);
     }
 
-    public void setIcon(QSTile.State state) {
-        setIcon((ImageView) mIcon, state);
+    public void setIcon(State state, boolean allowAnimations) {
+        setIcon((ImageView) mIcon, state, allowAnimations);
     }
 
-    protected void updateIcon(ImageView iv, State state) {
+    protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
         final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
         if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))
                 || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
-            boolean shouldAnimate = iv.isShown() && mAnimationEnabled
-                    && iv.getDrawable() != null;
+            boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
             Drawable d = icon != null
                     ? shouldAnimate ? icon.getDrawable(mContext)
                     : icon.getInvisibleDrawable(mContext) : null;
@@ -128,7 +127,11 @@
         }
     }
 
-    protected void setIcon(ImageView iv, QSTile.State state) {
+    private boolean shouldAnimate(ImageView iv) {
+        return mAnimationEnabled && iv.isShown() && iv.getDrawable() != null;
+    }
+
+    protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) {
         if (state.disabledByPolicy) {
             iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
         } else {
@@ -137,8 +140,8 @@
         if (state.state != mState) {
             int color = getColor(state.state);
             mState = state.state;
-            if (iv.isShown() && mTint != 0) {
-                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state));
+            if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
+                animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
                 mTint = color;
             } else {
                 if (iv instanceof AlphaControlledSlashImageView) {
@@ -148,10 +151,10 @@
                     setTint(iv, color);
                 }
                 mTint = color;
-                updateIcon(iv, state);
+                updateIcon(iv, state, allowAnimations);
             }
         } else {
-            updateIcon(iv, state);
+            updateIcon(iv, state, allowAnimations);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index 91afef02..d42127e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -47,6 +47,7 @@
 
     private static final String TAG = "QSTileBaseView";
     private final H mHandler = new H();
+    private final int[] mLocInScreen = new int[2];
     private final FrameLayout mIconFrame;
     protected QSIconView mIcon;
     protected RippleDrawable mRipple;
@@ -178,8 +179,9 @@
 
     protected void handleStateChanged(QSTile.State state) {
         int circleColor = getCircleColor(state.state);
+        boolean allowAnimations = animationsEnabled();
         if (circleColor != mCircleColor) {
-            if (mBg.isShown() && animationsEnabled()) {
+            if (allowAnimations) {
                 ValueAnimator animator = ValueAnimator.ofArgb(mCircleColor, circleColor)
                         .setDuration(QS_ANIM_LENGTH);
                 animator.addUpdateListener(animation -> mBg.setImageTintList(ColorStateList.valueOf(
@@ -192,7 +194,7 @@
         }
 
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
-        mIcon.setIcon(state);
+        mIcon.setIcon(state, allowAnimations);
         setContentDescription(state.contentDescription);
 
         mAccessibilityClass = state.expandedAccessibilityClassName;
@@ -205,8 +207,17 @@
         }
     }
 
+    /* The view should not be animated if it's not on screen and no part of it is visible.
+     */
     protected boolean animationsEnabled() {
-        return true;
+        if (!isShown()) {
+            return false;
+        }
+        if (getAlpha() != 1f) {
+            return false;
+        }
+        getLocationOnScreen(mLocInScreen);
+        return mLocInScreen[1] >= -getHeight();
     }
 
     private int getCircleColor(int state) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index f9f4f497..c5e4043 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -59,14 +59,14 @@
         // No current icon, only the static drawable should be used.
         s.icon = mock(Icon.class);
         when(iv.getDrawable()).thenReturn(null);
-        mIconView.updateIcon(iv, s);
+        mIconView.updateIcon(iv, s, true);
         verify(s.icon, never()).getDrawable(any());
         verify(s.icon).getInvisibleDrawable(any());
 
         // Has icon, should use the standard (animated) form.
         s.icon = mock(Icon.class);
         when(iv.getDrawable()).thenReturn(mock(Drawable.class));
-        mIconView.updateIcon(iv, s);
+        mIconView.updateIcon(iv, s, true);
         verify(s.icon).getDrawable(any());
         verify(s.icon, never()).getInvisibleDrawable(any());
     }
@@ -79,7 +79,7 @@
         int desiredColor = mIconView.getColor(s.state);
         when(iv.isShown()).thenReturn(true);
 
-        mIconView.setIcon(iv, s);
+        mIconView.setIcon(iv, s, true);
         verify(iv).setImageTintList(argThat(stateList -> stateList.getColors()[0] == desiredColor));
     }
 }