Animate public/private notification layouts

This change move the boolean whether we hide sensitive contents into
AmbientState, which makes it consistent with the other stack states
and allows for a orchestrated transition between public/private
layouts. We need this transition when going into the full shade.

Bug: 16291973
Change-Id: I379a6119b5b73eca900a4a2ba9d5ec95b293e487
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 4b0af11..0960c00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -37,6 +37,9 @@
     private boolean mUserLocked;
     /** Are we showing the "public" version */
     private boolean mShowingPublic;
+    private boolean mSensitive;
+    private boolean mShowingPublicInitialized;
+    private boolean mShowingPublicForIntrinsicHeight;
 
     /**
      * Is this notification expanded by the system. The expansion state can be overridden by the
@@ -78,6 +81,8 @@
         mHasUserChangedExpansion = false;
         mUserLocked = false;
         mShowingPublic = false;
+        mSensitive = false;
+        mShowingPublicInitialized = false;
         mIsSystemExpanded = false;
         mExpansionDisabled = false;
         mPublicLayout.reset();
@@ -222,7 +227,7 @@
             return mRowMinHeight;
         }
 
-        return mShowingPublic ? mRowMinHeight : getMaxExpandHeight();
+        return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
     }
 
     /**
@@ -248,17 +253,64 @@
         }
     }
 
-    public void setShowingPublic(boolean show) {
-        mShowingPublic = show;
+    public void setSensitive(boolean sensitive) {
+        mSensitive = sensitive;
+    }
+
+    public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
+        mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive;
+    }
+
+    public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
+            long duration) {
+        boolean oldShowingPublic = mShowingPublic;
+        mShowingPublic = mSensitive && hideSensitive;
+        if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
+            return;
+        }
 
         // bail out if no public version
         if (mPublicLayout.getChildCount() == 0) return;
 
-        // TODO: animation?
-        mPublicLayout.setVisibility(show ? View.VISIBLE : View.GONE);
-        mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE);
+        if (!animated) {
+            mPublicLayout.animate().cancel();
+            mPrivateLayout.animate().cancel();
+            mPublicLayout.setAlpha(1f);
+            mPrivateLayout.setAlpha(1f);
+            mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
+            mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
+        } else {
+            animateShowingPublic(delay, duration);
+        }
 
         updateVetoButton();
+        mShowingPublicInitialized = true;
+    }
+
+    private void animateShowingPublic(long delay, long duration) {
+        final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
+        View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
+        source.setVisibility(View.VISIBLE);
+        target.setVisibility(View.VISIBLE);
+        target.setAlpha(0f);
+        source.animate().cancel();
+        target.animate().cancel();
+        source.animate()
+                .alpha(0f)
+                .withLayer()
+                .setStartDelay(delay)
+                .setDuration(duration)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        source.setVisibility(View.INVISIBLE);
+                    }
+                });
+        target.animate()
+                .alpha(1f)
+                .withLayer()
+                .setStartDelay(delay)
+                .setDuration(duration);
     }
 
     private void updateVetoButton() {
@@ -267,7 +319,7 @@
     }
 
     public int getMaxExpandHeight() {
-        return mShowingPublic ? mRowMinHeight : mMaxExpandHeight;
+        return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 5cadd1e..46d4a9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -176,6 +176,23 @@
     }
 
     /**
+     * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
+     * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
+     * of a stack scroller update such that the updated intrinsic height (which is dependent on
+     * whether private or public layout is showing) gets taken into account into all layout
+     * calculations.
+     */
+    public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
+    }
+
+    /**
+     * Sets whether the notification should hide its private contents if it is sensitive.
+     */
+    public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
+            long duration) {
+    }
+
+    /**
      * @return The desired notification height.
      */
     public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 61b0eaa..7d4f90e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1362,10 +1362,15 @@
             int vis = ent.notification.getNotification().visibility;
 
             // Display public version of the notification if we need to redact.
-            final boolean hideSensitive = shouldHideSensitiveContents(ent.notification.getUserId());
-            boolean showingPublic = vis == Notification.VISIBILITY_PRIVATE && hideSensitive;
-            ent.row.setShowingPublic(showingPublic);
+            final boolean hideSensitive =
+                    !userAllowsPrivateNotificationsInPublic(ent.notification.getUserId());
+            boolean sensitive = vis == Notification.VISIBILITY_PRIVATE;
+            boolean showingPublic = sensitive && hideSensitive && isLockscreenPublicMode();
+            ent.row.setSensitive(sensitive && hideSensitive);
             if (ent.autoRedacted && ent.legacy) {
+
+                // TODO: Also fade this? Or, maybe easier (and better), provide a dark redacted form
+                // for legacy auto redacted notifications.
                 if (showingPublic) {
                     ent.row.setShowingLegacyBackground(false);
                 } else {
@@ -3415,8 +3420,8 @@
         }
         mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
         updateDozingState();
-        updateStackScrollerState();
         updatePublicMode();
+        updateStackScrollerState(goingToFullShade);
         updateNotifications();
         checkBarModes();
         updateCarrierLabelVisibility(false);
@@ -3443,9 +3448,10 @@
         mScrimController.setDozing(mDozing);
     }
 
-    public void updateStackScrollerState() {
+    public void updateStackScrollerState(boolean goingToFullShade) {
         if (mStackScroller == null) return;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
+        mStackScroller.setHideSensitive(isLockscreenPublicMode(), goingToFullShade);
         mStackScroller.setDimmed(onKeyguard, false /* animate */);
         mStackScroller.setExpandingEnabled(!onKeyguard);
         ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index c2fa68f..2aceb95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -91,7 +91,9 @@
 
         if (mHeadsUp != null) {
             mHeadsUp.row.setSystemExpanded(true);
-            mHeadsUp.row.setShowingPublic(false);
+            mHeadsUp.row.setSensitive(false);
+            mHeadsUp.row.setHideSensitive(
+                    false, false /* animated */, 0 /* delay */, 0 /* duration */);
             if (mContentHolder == null) {
                 // too soon!
                 return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 0582140..ddb5cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -34,6 +34,7 @@
     private int mSpeedBumpIndex = -1;
     private float mScrimAmount;
     private boolean mDark;
+    private boolean mHideSensitive;
 
     public int getScrollY() {
         return mScrollY;
@@ -68,6 +69,10 @@
         mDark = dark;
     }
 
+    public void setHideSensitive(boolean hideSensitive) {
+        mHideSensitive = hideSensitive;
+    }
+
     /**
      * In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
      * interaction. This child is then scaled normally and its background is fully opaque.
@@ -84,6 +89,10 @@
         return mDark;
     }
 
+    public boolean isHideSensitive() {
+        return mHideSensitive;
+    }
+
     public ActivatableNotificationView getActivatedChild() {
         return mActivatedChild;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index 2709384..3c93b19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -30,6 +30,7 @@
     boolean animateTopInset;
     boolean animateDimmed;
     boolean animateDark;
+    boolean animateHideSensitive;
     boolean hasDelays;
     boolean hasGoToFullShadeEvent;
 
@@ -78,6 +79,11 @@
         return this;
     }
 
+    public AnimationFilter animateHideSensitive() {
+        animateHideSensitive = true;
+        return this;
+    }
+
     /**
      * Combines multiple filters into {@code this} filter, using or as the operand .
      *
@@ -104,6 +110,7 @@
         animateTopInset |= filter.animateTopInset;
         animateDimmed |= filter.animateDimmed;
         animateDark |= filter.animateDark;
+        animateHideSensitive |= filter.animateHideSensitive;
         hasDelays |= filter.hasDelays;
     }
 
@@ -116,6 +123,7 @@
         animateTopInset = false;
         animateDimmed = false;
         animateDark = false;
+        animateHideSensitive = false;
         hasDelays = false;
         hasGoToFullShadeEvent = false;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index c9bfc4e..a4d2021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -131,6 +131,7 @@
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
     private boolean mDimmedNeedsAnimation;
+    private boolean mHideSensitiveNeedsAnimation;
     private boolean mDarkNeedsAnimation;
     private boolean mActivateNeedsAnimation;
     private boolean mGoToFullShadeNeedsAnimation;
@@ -1579,6 +1580,7 @@
         generateTopPaddingEvent();
         generateActivateEvent();
         generateDimmedEvent();
+        generateHideSensitiveEvent();
         generateDarkEvent();
         generateGoToFullShadeEvent();
         mNeedsAnimation = false;
@@ -1656,6 +1658,14 @@
         mDimmedNeedsAnimation = false;
     }
 
+    private void generateHideSensitiveEvent() {
+        if (mHideSensitiveNeedsAnimation) {
+            mAnimationEvents.add(
+                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
+        }
+        mHideSensitiveNeedsAnimation = false;
+    }
+
     private void generateDarkEvent() {
         if (mDarkNeedsAnimation) {
             mAnimationEvents.add(
@@ -1899,6 +1909,22 @@
         requestChildrenUpdate();
     }
 
+    public void setHideSensitive(boolean hideSensitive, boolean animate) {
+        if (hideSensitive != mAmbientState.isHideSensitive()) {
+            int childCount = getChildCount();
+            for (int i = 0; i < childCount; i++) {
+                ExpandableView v = (ExpandableView) getChildAt(i);
+                v.setHideSensitiveForIntrinsicHeight(hideSensitive);
+            }
+            mAmbientState.setHideSensitive(hideSensitive);
+            if (animate && mAnimationsEnabled) {
+                mHideSensitiveNeedsAnimation = true;
+                mNeedsAnimation =  true;
+            }
+            requestChildrenUpdate();
+        }
+    }
+
     /**
      * See {@link AmbientState#setActivatedChild}.
      */
@@ -2155,7 +2181,12 @@
                         .animateY()
                         .animateDimmed()
                         .animateScale()
-                        .animateZ(),
+                        .animateZ()
+                        .hasDelays(),
+
+                // ANIMATION_TYPE_HIDE_SENSITIVE
+                new AnimationFilter()
+                        .animateHideSensitive(),
         };
 
         static int[] LENGTHS = new int[] {
@@ -2192,6 +2223,9 @@
 
                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
+
+                // ANIMATION_TYPE_HIDE_SENSITIVE
+                StackStateAnimator.ANIMATION_DURATION_STANDARD,
         };
 
         static final int ANIMATION_TYPE_ADD = 0;
@@ -2205,6 +2239,7 @@
         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
         static final int ANIMATION_TYPE_DARK = 9;
         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
+        static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
 
         final long eventStartTime;
         final View changingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index fe2733b..ba3f339 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -159,7 +159,7 @@
         updateZValuesForState(resultState, algorithmState);
 
         handleDraggedViews(ambientState, resultState, algorithmState);
-        updateDimmedActivated(ambientState, resultState, algorithmState);
+        updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState);
         updateScrimAmount(resultState, algorithmState, ambientState.getScrimAmount());
         updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
@@ -251,12 +251,13 @@
     }
 
     /**
-     * Updates the dimmed and activated states of the children.
+     * Updates the dimmed, activated and hiding sensitive states of the children.
      */
-    private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState) {
+    private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
+            StackScrollState resultState, StackScrollAlgorithmState algorithmState) {
         boolean dimmed = ambientState.isDimmed();
         boolean dark = ambientState.isDark();
+        boolean hideSensitive = ambientState.isHideSensitive();
         View activatedChild = ambientState.getActivatedChild();
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
@@ -264,6 +265,7 @@
             StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
             childViewState.dimmed = dimmed;
             childViewState.dark = dark;
+            childViewState.hideSensitive = hideSensitive;
             boolean isActivatedChild = activatedChild == child;
             childViewState.scale = !dimmed || isActivatedChild
                     ? 1.0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index d0064c8..a174952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -157,6 +157,10 @@
                 // apply dark
                 child.setDark(state.dark, false /* animate */);
 
+                // apply hiding sensitive
+                child.setHideSensitive(
+                        state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
+
                 // apply speed bump state
                 child.setBelowSpeedBump(state.belowSpeedBump);
 
@@ -238,6 +242,7 @@
         float scale;
         boolean dimmed;
         boolean dark;
+        boolean hideSensitive;
         boolean belowSpeedBump;
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index e21d52d..afd7216 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -220,6 +220,10 @@
         // apply speed bump state
         child.setBelowSpeedBump(viewState.belowSpeedBump);
 
+        // start hiding sensitive animation
+        child.setHideSensitive(viewState.hideSensitive,
+                mAnimationFilter.animateHideSensitive && !wasAdded, delay, duration);
+
         // apply scrimming
         child.setScrimAmount(viewState.scrimAmount);