1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.statusbar.notification.row;
18 
19 import android.animation.AnimatorListenerAdapter;
20 import android.content.Context;
21 import android.util.AttributeSet;
22 import android.view.View;
23 import android.view.animation.Interpolator;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.systemui.animation.Interpolators;
27 
28 /**
29  * A common base class for all views in the notification stack scroller which don't have a
30  * background.
31  */
32 public abstract class StackScrollerDecorView extends ExpandableView {
33 
34     protected View mContent;
35     protected View mSecondaryView;
36     private boolean mIsVisible = true;
37     private boolean mContentVisible = true;
38     private boolean mIsSecondaryVisible = true;
39     private int mDuration = 260;
40     private boolean mContentAnimating;
41     private final Runnable mContentVisibilityEndRunnable = () -> {
42         mContentAnimating = false;
43         if (getVisibility() != View.GONE && !mIsVisible) {
44             setVisibility(GONE);
45             setWillBeGone(false);
46             notifyHeightChanged(false /* needsAnimation */);
47         }
48     };
49 
50     private boolean mSecondaryAnimating = false;
51     private final Runnable mSecondaryVisibilityEndRunnable = () -> {
52         mSecondaryAnimating = false;
53         // If we were on screen, become GONE to avoid touches
54         if (mSecondaryView == null) return;
55         if (getVisibility() != View.GONE
56                 && mSecondaryView.getVisibility() != View.GONE
57                 && !mIsSecondaryVisible) {
58             mSecondaryView.setVisibility(View.GONE);
59         }
60     };
61 
StackScrollerDecorView(Context context, AttributeSet attrs)62     public StackScrollerDecorView(Context context, AttributeSet attrs) {
63         super(context, attrs);
64         setClipChildren(false);
65     }
66 
67     @Override
onFinishInflate()68     protected void onFinishInflate() {
69         super.onFinishInflate();
70         mContent = findContentView();
71         mSecondaryView = findSecondaryView();
72         setVisible(false /* nowVisible */, false /* animate */);
73         setSecondaryVisible(false /* nowVisible */, false /* animate */);
74     }
75 
76     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)77     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
78         super.onLayout(changed, left, top, right, bottom);
79         setOutlineProvider(null);
80     }
81 
82     @Override
isTransparent()83     public boolean isTransparent() {
84         return true;
85     }
86 
87     /**
88      * @param visible True if we should animate contents visible
89      */
setContentVisible(boolean visible)90     public void setContentVisible(boolean visible) {
91         setContentVisible(visible, true /* animate */, null /* runAfter */);
92     }
93 
94     /**
95      * @param visible True if the contents should be visible
96      * @param animate True if we should fade to new visibility
97      * @param runAfter Runnable to run after visibility updates
98      */
setContentVisible(boolean visible, boolean animate, Runnable runAfter)99     public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
100         if (mContentVisible != visible) {
101             mContentAnimating = animate;
102             mContentVisible = visible;
103             Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
104                 mContentVisibilityEndRunnable.run();
105                 runAfter.run();
106             };
107             setViewVisible(mContent, visible, animate, endRunnable);
108         }
109 
110         if (!mContentAnimating) {
111             mContentVisibilityEndRunnable.run();
112         }
113     }
114 
isContentVisible()115     public boolean isContentVisible() {
116         return mContentVisible;
117     }
118 
setVisible(boolean nowVisible, boolean animate)119     public void setVisible(boolean nowVisible, boolean animate) {
120         setVisible(nowVisible, animate, null);
121     }
122 
123     /**
124      * Make this view visible. If {@code false} is passed, the view will fade out it's content
125      * and set the view Visibility to GONE. If only the content should be changed
126      * {@link #setContentVisible(boolean)} can be used.
127      *
128      * @param nowVisible should the view be visible
129      * @param animate should the change be animated.
130      */
setVisible(boolean nowVisible, boolean animate, Runnable runAfter)131     public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
132         if (mIsVisible != nowVisible) {
133             mIsVisible = nowVisible;
134             if (animate) {
135                 if (nowVisible) {
136                     setVisibility(VISIBLE);
137                     setWillBeGone(false);
138                     notifyHeightChanged(false /* needsAnimation */);
139                 } else {
140                     setWillBeGone(true);
141                 }
142                 setContentVisible(nowVisible, true /* animate */, runAfter);
143             } else {
144                 setVisibility(nowVisible ? VISIBLE : GONE);
145                 setContentVisible(nowVisible, false /* animate */, runAfter);
146                 setWillBeGone(false);
147                 notifyHeightChanged(false /* needsAnimation */);
148             }
149         }
150     }
151 
152     /**
153      * Set the secondary view of this layout to visible.
154      *
155      * @param nowVisible should the secondary view be visible
156      * @param animate should the change be animated
157      */
setSecondaryVisible(boolean nowVisible, boolean animate)158     public void setSecondaryVisible(boolean nowVisible, boolean animate) {
159         if (mIsSecondaryVisible != nowVisible) {
160             mSecondaryAnimating = animate;
161             mIsSecondaryVisible = nowVisible;
162             setViewVisible(mSecondaryView, nowVisible, animate, mSecondaryVisibilityEndRunnable);
163         }
164 
165         if (!mSecondaryAnimating) {
166             mSecondaryVisibilityEndRunnable.run();
167         }
168     }
169 
170     @VisibleForTesting
isSecondaryVisible()171     boolean isSecondaryVisible() {
172         return mIsSecondaryVisible;
173     }
174 
175     /**
176      * Is this view visible. If a view is currently animating to gone, it will
177      * return {@code false}.
178      */
isVisible()179     public boolean isVisible() {
180         return mIsVisible;
181     }
182 
setDuration(int duration)183     void setDuration(int duration) {
184         mDuration = duration;
185     }
186 
187     /**
188      * Animate a view to a new visibility.
189      * @param view Target view, maybe content view or dismiss view.
190      * @param nowVisible Should it now be visible.
191      * @param animate Should this be done in an animated way.
192      * @param endRunnable A runnable that is run when the animation is done.
193      */
setViewVisible(View view, boolean nowVisible, boolean animate, Runnable endRunnable)194     private void setViewVisible(View view, boolean nowVisible,
195             boolean animate, Runnable endRunnable) {
196         if (view == null) {
197             return;
198         }
199 
200         // Make sure we're visible so animations work
201         if (view.getVisibility() != View.VISIBLE) {
202             view.setVisibility(View.VISIBLE);
203         }
204 
205         // cancel any previous animations
206         view.animate().cancel();
207         float endValue = nowVisible ? 1.0f : 0.0f;
208         if (!animate) {
209             view.setAlpha(endValue);
210             if (endRunnable != null) {
211                 endRunnable.run();
212             }
213             return;
214         }
215 
216         // Animate the view alpha
217         Interpolator interpolator = nowVisible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT;
218         view.animate()
219                 .alpha(endValue)
220                 .setInterpolator(interpolator)
221                 .setDuration(mDuration)
222                 .withEndAction(endRunnable);
223     }
224 
225     @Override
performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)226     public long performRemoveAnimation(long duration, long delay,
227             float translationDirection, boolean isHeadsUpAnimation, float endLocation,
228             Runnable onFinishedRunnable,
229             AnimatorListenerAdapter animationListener) {
230         // TODO: Use duration
231         setContentVisible(false);
232         return 0;
233     }
234 
235     @Override
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)236     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
237         // TODO: use delay and duration
238         setContentVisible(true);
239     }
240 
241     @Override
needsClippingToShelf()242     public boolean needsClippingToShelf() {
243         return false;
244     }
245 
246     @Override
hasOverlappingRendering()247     public boolean hasOverlappingRendering() {
248         return false;
249     }
250 
findContentView()251     protected abstract View findContentView();
252 
253     /**
254      * Returns a view that might not always appear while the main content view is still visible.
255      */
findSecondaryView()256     protected abstract View findSecondaryView();
257 }
258