Move PIN/Pattern appear animation to RenderThread (1/2)

Bug: 22205322
Change-Id: I253f20a397ab89cb78254338a0254619232d7300
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 444f878..a709bb8e 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -23,10 +23,10 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.CanvasProperty;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Debug;
@@ -38,8 +38,10 @@
 import android.util.AttributeSet;
 import android.util.IntArray;
 import android.util.Log;
+import android.view.DisplayListCanvas;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -200,10 +202,16 @@
     }
 
     public static class CellState {
-        public float scale = 1.0f;
-        public float translateY = 0.0f;
-        public float alpha = 1.0f;
-        public float size;
+        int row;
+        int col;
+        boolean hwAnimating;
+        CanvasProperty<Float> hwRadius;
+        CanvasProperty<Float> hwCenterX;
+        CanvasProperty<Float> hwCenterY;
+        CanvasProperty<Paint> hwPaint;
+        float radius;
+        float translationY;
+        float alpha = 1f;
         public float lineEndX = Float.MIN_VALUE;
         public float lineEndY = Float.MIN_VALUE;
         public ValueAnimator lineAnimator;
@@ -313,7 +321,9 @@
         for (int i = 0; i < 3; i++) {
             for (int j = 0; j < 3; j++) {
                 mCellStates[i][j] = new CellState();
-                mCellStates[i][j].size = mDotSize;
+                mCellStates[i][j].radius = mDotSize/2;
+                mCellStates[i][j].row = i;
+                mCellStates[i][j].col = j;
             }
         }
 
@@ -412,6 +422,112 @@
         invalidate();
     }
 
+    public void startCellStateAnimation(CellState cellState, float startAlpha, float endAlpha,
+            float startTranslationY, float endTranslationY, float startScale, float endScale,
+            long delay, long duration,
+            Interpolator interpolator, Runnable finishRunnable) {
+        if (isHardwareAccelerated()) {
+            startCellStateAnimationHw(cellState, startAlpha, endAlpha, startTranslationY,
+                    endTranslationY, startScale, endScale, delay, duration, interpolator,
+                    finishRunnable);
+        } else {
+            startCellStateAnimationSw(cellState, startAlpha, endAlpha, startTranslationY,
+                    endTranslationY, startScale, endScale, delay, duration, interpolator,
+                    finishRunnable);
+        }
+    }
+
+    private void startCellStateAnimationSw(final CellState cellState,
+            final float startAlpha, final float endAlpha,
+            final float startTranslationY, final float endTranslationY,
+            final float startScale, final float endScale,
+            long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
+        cellState.alpha = startAlpha;
+        cellState.translationY = startTranslationY;
+        cellState.radius = mDotSize/2 * startScale;
+        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = (float) animation.getAnimatedValue();
+                cellState.alpha = (1 - t) * startAlpha + t * endAlpha;
+                cellState.translationY = (1 - t) * startTranslationY + t * endTranslationY;
+                cellState.radius = mDotSize/2 * ((1 - t) * startScale + t * endScale);
+                invalidate();
+            }
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (finishRunnable != null) {
+                    finishRunnable.run();
+                }
+            }
+        });
+        animator.start();
+    }
+
+    private void startCellStateAnimationHw(final CellState cellState,
+            float startAlpha, float endAlpha,
+            float startTranslationY, float endTranslationY,
+            float startScale, float endScale,
+            long delay, long duration, Interpolator interpolator, final Runnable finishRunnable) {
+        cellState.alpha = endAlpha;
+        cellState.translationY = endTranslationY;
+        cellState.radius = mDotSize/2 * endScale;
+        cellState.hwAnimating = true;
+        cellState.hwCenterY = CanvasProperty.createFloat(
+                getCenterYForRow(cellState.row) + startTranslationY);
+        cellState.hwCenterX = CanvasProperty.createFloat(getCenterXForColumn(cellState.col));
+        cellState.hwRadius = CanvasProperty.createFloat(mDotSize/2 * startScale);
+        mPaint.setColor(getCurrentColor(false));
+        mPaint.setAlpha((int) (startAlpha * 255));
+        cellState.hwPaint = CanvasProperty.createPaint(new Paint(mPaint));
+
+        startRtFloatAnimation(cellState.hwCenterY,
+                getCenterYForRow(cellState.row) + endTranslationY, delay, duration, interpolator);
+        startRtFloatAnimation(cellState.hwRadius, mDotSize/2 * endScale, delay, duration,
+                interpolator);
+        startRtAlphaAnimation(cellState, endAlpha, delay, duration, interpolator,
+                new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        cellState.hwAnimating = false;
+                        if (finishRunnable != null) {
+                            finishRunnable.run();
+                        }
+                    }
+                });
+
+        invalidate();
+    }
+
+    private void startRtAlphaAnimation(CellState cellState, float endAlpha,
+            long delay, long duration, Interpolator interpolator,
+            Animator.AnimatorListener listener) {
+        RenderNodeAnimator animator = new RenderNodeAnimator(cellState.hwPaint,
+                RenderNodeAnimator.PAINT_ALPHA, (int) (endAlpha * 255));
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.setTarget(this);
+        animator.addListener(listener);
+        animator.start();
+    }
+
+    private void startRtFloatAnimation(CanvasProperty<Float> property, float endValue,
+            long delay, long duration, Interpolator interpolator) {
+        RenderNodeAnimator animator = new RenderNodeAnimator(property, endValue);
+        animator.setDuration(duration);
+        animator.setStartDelay(delay);
+        animator.setInterpolator(interpolator);
+        animator.setTarget(this);
+        animator.start();
+    }
+
     private void notifyCellAdded() {
         // sendAccessEvent(R.string.lockscreen_access_pattern_cell_added);
         if (mOnPatternListener != null) {
@@ -603,14 +719,15 @@
 
     private void startCellActivatedAnimation(Cell cell) {
         final CellState cellState = mCellStates[cell.row][cell.column];
-        startSizeAnimation(mDotSize, mDotSizeActivated, 96, mLinearOutSlowInInterpolator,
+        startRadiusAnimation(mDotSize/2, mDotSizeActivated/2, 96, mLinearOutSlowInInterpolator,
                 cellState, new Runnable() {
-            @Override
-            public void run() {
-                startSizeAnimation(mDotSizeActivated, mDotSize, 192, mFastOutSlowInInterpolator,
-                        cellState, null);
-            }
-        });
+                    @Override
+                    public void run() {
+                        startRadiusAnimation(mDotSizeActivated/2, mDotSize/2, 192,
+                                mFastOutSlowInInterpolator,
+                                cellState, null);
+                    }
+                });
         startLineEndAnimation(cellState, mInProgressX, mInProgressY,
                 getCenterXForColumn(cell.column), getCenterYForRow(cell.row));
     }
@@ -639,13 +756,13 @@
         state.lineAnimator = valueAnimator;
     }
 
-    private void startSizeAnimation(float start, float end, long duration, Interpolator interpolator,
-            final CellState state, final Runnable endRunnable) {
+    private void startRadiusAnimation(float start, float end, long duration,
+            Interpolator interpolator, final CellState state, final Runnable endRunnable) {
         ValueAnimator valueAnimator = ValueAnimator.ofFloat(start, end);
         valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                state.size = (float) animation.getAnimatedValue();
+                state.radius = (float) animation.getAnimatedValue();
                 invalidate();
             }
         });
@@ -969,10 +1086,16 @@
             for (int j = 0; j < 3; j++) {
                 CellState cellState = mCellStates[i][j];
                 float centerX = getCenterXForColumn(j);
-                float size = cellState.size * cellState.scale;
-                float translationY = cellState.translateY;
-                drawCircle(canvas, (int) centerX, (int) centerY + translationY,
-                        size, drawLookup[i][j], cellState.alpha);
+                float translationY = cellState.translationY;
+                if (isHardwareAccelerated() && cellState.hwAnimating) {
+                    DisplayListCanvas displayListCanvas = (DisplayListCanvas) canvas;
+                    displayListCanvas.drawCircle(cellState.hwCenterX, cellState.hwCenterY,
+                            cellState.hwRadius, cellState.hwPaint);
+                } else {
+                    drawCircle(canvas, (int) centerX, (int) centerY + translationY,
+                            cellState.radius, drawLookup[i][j], cellState.alpha);
+
+                }
             }
         }
 
@@ -1055,11 +1178,11 @@
     /**
      * @param partOfPattern Whether this circle is part of the pattern.
      */
-    private void drawCircle(Canvas canvas, float centerX, float centerY, float size,
+    private void drawCircle(Canvas canvas, float centerX, float centerY, float radius,
             boolean partOfPattern, float alpha) {
         mPaint.setColor(getCurrentColor(partOfPattern));
         mPaint.setAlpha((int) (alpha * 255));
-        canvas.drawCircle(centerX, centerY, size/2, mPaint);
+        canvas.drawCircle(centerX, centerY, radius, mPaint);
     }
 
     @Override
@@ -1290,7 +1413,6 @@
             float centerY = getCenterYForRow(row);
             float cellheight = mSquareHeight * mHitFactor * 0.5f;
             float cellwidth = mSquareWidth * mHitFactor * 0.5f;
-            float translationY = cell.translateY;
             bounds.left = (int) (centerX - cellwidth);
             bounds.right = (int) (centerX + cellwidth);
             bounds.top = (int) (centerY - cellheight);
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
index d265e0d..4abb795 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
@@ -16,8 +16,12 @@
 
 package com.android.keyguard;
 
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.RenderNode;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
@@ -114,10 +118,8 @@
         enableClipping(false);
         setAlpha(1f);
         setTranslationY(mAppearAnimationUtils.getStartTranslation());
-        animate()
-                .setDuration(500)
-                .setInterpolator(mAppearAnimationUtils.getInterpolator())
-                .translationY(0);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+                0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(mViews,
                 new Runnable() {
                     @Override
@@ -131,10 +133,8 @@
     public boolean startDisappearAnimation(final Runnable finishRunnable) {
         enableClipping(false);
         setTranslationY(0);
-        animate()
-                .setDuration(280)
-                .setInterpolator(mDisappearAnimationUtils.getInterpolator())
-                .translationY(mDisappearYTranslation);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
+                mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
         mDisappearAnimationUtils.startAnimation2d(mViews,
                 new Runnable() {
                     @Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index 3568429..b000e26 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -17,6 +17,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.graphics.Rect;
@@ -27,6 +28,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.RenderNodeAnimator;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AnimationUtils;
@@ -334,10 +336,8 @@
         enableClipping(false);
         setAlpha(1f);
         setTranslationY(mAppearAnimationUtils.getStartTranslation());
-        animate()
-                .setDuration(500)
-                .setInterpolator(mAppearAnimationUtils.getInterpolator())
-                .translationY(0);
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
+                0, mAppearAnimationUtils.getInterpolator());
         mAppearAnimationUtils.startAnimation2d(
                 mLockPatternView.getCellStates(),
                 new Runnable() {
@@ -362,10 +362,9 @@
         mLockPatternView.clearPattern();
         enableClipping(false);
         setTranslationY(0);
-        animate()
-                .setDuration(300)
-                .setInterpolator(mDisappearAnimationUtils.getInterpolator())
-                .translationY(-mDisappearAnimationUtils.getStartTranslation());
+        AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
+                -mDisappearAnimationUtils.getStartTranslation(),
+                mDisappearAnimationUtils.getInterpolator());
         mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
                 new Runnable() {
                     @Override
@@ -398,43 +397,16 @@
             long duration, float translationY, final boolean appearing,
             Interpolator interpolator,
             final Runnable finishListener) {
-        if (appearing) {
-            animatedCell.scale = 0.0f;
-            animatedCell.alpha = 1.0f;
-        }
-        animatedCell.translateY = appearing ? translationY : 0;
-        ValueAnimator animator = ValueAnimator.ofFloat(animatedCell.translateY,
-                appearing ? 0 : translationY);
-        animator.setInterpolator(interpolator);
-        animator.setDuration(duration);
-        animator.setStartDelay(delay);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float animatedFraction = animation.getAnimatedFraction();
-                if (appearing) {
-                    animatedCell.scale = animatedFraction;
-                } else {
-                    animatedCell.alpha = 1 - animatedFraction;
-                }
-                animatedCell.translateY = (float) animation.getAnimatedValue();
-                mLockPatternView.invalidate();
-            }
-        });
+        mLockPatternView.startCellStateAnimation(animatedCell,
+                1f, appearing ? 1f : 0f, /* alpha */
+                appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
+                appearing ? 0f : 1f, 1f /* scale */,
+                delay, duration, interpolator, finishListener);
         if (finishListener != null) {
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    finishListener.run();
-                }
-            });
-
             // Also animate the Emergency call
             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
                     appearing, interpolator, null);
         }
-        animator.start();
-        mLockPatternView.invalidate();
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
index 441474d..df76125 100644
--- a/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/animation/AppearAnimationUtils.java
@@ -16,11 +16,18 @@
 
 package com.android.settingslib.animation;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.view.RenderNodeAnimator;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import com.android.internal.widget.LockPatternView;
 import com.android.settingslib.R;
 
 /**
@@ -174,26 +181,65 @@
     }
 
     @Override
-    public void createAnimation(View view, long delay, long duration, float translationY,
-            boolean appearing, Interpolator interpolator, Runnable endRunnable) {
+    public void createAnimation(final View view, long delay, long duration, float translationY,
+            boolean appearing, Interpolator interpolator, final Runnable endRunnable) {
         if (view != null) {
             view.setAlpha(appearing ? 0f : 1.0f);
             view.setTranslationY(appearing ? translationY : 0);
-            view.animate()
-                    .alpha(appearing ? 1f : 0f)
-                    .translationY(appearing ? 0 : translationY)
-                    .setInterpolator(interpolator)
-                    .setDuration(duration)
-                    .setStartDelay(delay);
+            Animator alphaAnim;
+            float targetAlpha =  appearing ? 1f : 0f;
+            if (view.isHardwareAccelerated()) {
+                RenderNodeAnimator alphaAnimRt = new RenderNodeAnimator(RenderNodeAnimator.ALPHA,
+                        targetAlpha);
+                alphaAnimRt.setTarget(view);
+                alphaAnim = alphaAnimRt;
+            } else {
+                alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, view.getAlpha(), targetAlpha);
+            }
+            alphaAnim.setInterpolator(interpolator);
+            alphaAnim.setDuration(duration);
+            alphaAnim.setStartDelay(delay);
             if (view.hasOverlappingRendering()) {
-                view.animate().withLayer();
+                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                alphaAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setLayerType(View.LAYER_TYPE_NONE, null);
+                    }
+                });
             }
             if (endRunnable != null) {
-                view.animate().withEndAction(endRunnable);
+                alphaAnim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        endRunnable.run();
+                    }
+                });
             }
+            alphaAnim.start();
+            startTranslationYAnimation(view, delay, duration, appearing ? 0 : translationY,
+                    interpolator);
         }
     }
 
+    public static void startTranslationYAnimation(View view, long delay, long duration,
+            float endTranslationY, Interpolator interpolator) {
+        Animator translationAnim;
+        if (view.isHardwareAccelerated()) {
+            RenderNodeAnimator translationAnimRt = new RenderNodeAnimator(
+                    RenderNodeAnimator.TRANSLATION_Y, endTranslationY);
+            translationAnimRt.setTarget(view);
+            translationAnim = translationAnimRt;
+        } else {
+            translationAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y,
+                    view.getTranslationY(), endTranslationY);
+        }
+        translationAnim.setInterpolator(interpolator);
+        translationAnim.setDuration(duration);
+        translationAnim.setStartDelay(delay);
+        translationAnim.start();
+    }
+
     public class AppearAnimationProperties {
         public long[][] delays;
         public int maxDelayRowIndex;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 10191ed..bbd57db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -26,11 +26,13 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
@@ -42,6 +44,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.EventLogConstants;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.R;
@@ -79,6 +82,8 @@
     private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
     private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
 
+    private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
+
     public static final long DOZE_ANIMATION_DURATION = 700;
 
     private KeyguardAffordanceHelper mAfforanceHelper;
@@ -1776,7 +1781,22 @@
         mIsExpanding = false;
         mScrollYOverride = -1;
         if (isFullyCollapsed()) {
-            setListening(false);
+            DejankUtils.postAfterTraversal(new Runnable() {
+                @Override
+                public void run() {
+                    setListening(false);
+                }
+            });
+
+            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
+            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
+            // ahead with rendering and we jank.
+            postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
+                }
+            });
         } else {
             setListening(true);
         }