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.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 
StackScrollerDecorView(Context context, AttributeSet attrs)50     public StackScrollerDecorView(Context context, AttributeSet attrs) {
51         super(context, attrs);
52     }
53 
54     @Override
onFinishInflate()55     protected void onFinishInflate() {
56         super.onFinishInflate();
57         mContent = findContentView();
58         mSecondaryView = findSecondaryView();
59         setVisible(false /* nowVisible */, false /* animate */);
60         setSecondaryVisible(false /* nowVisible */, false /* animate */);
61     }
62 
63     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)64     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
65         super.onLayout(changed, left, top, right, bottom);
66         setOutlineProvider(null);
67     }
68 
69     @Override
isTransparent()70     public boolean isTransparent() {
71         return true;
72     }
73 
74     /**
75      * Set the content of this view to be visible in an animated way.
76      *
77      * @param contentVisible True if the content should be visible or false if it should be hidden.
78      */
setContentVisible(boolean contentVisible)79     public void setContentVisible(boolean contentVisible) {
80         setContentVisible(contentVisible, true /* animate */);
81     }
82     /**
83      * Set the content of this view to be visible.
84      * @param contentVisible True if the content should be visible or false if it should be hidden.
85      * @param animate Should an animation be performed.
86      */
setContentVisible(boolean contentVisible, boolean animate)87     private void setContentVisible(boolean contentVisible, boolean animate) {
88         if (mContentVisible != contentVisible) {
89             mContentAnimating = animate;
90             setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
91             mContentVisible = contentVisible;
92         } if (!mContentAnimating) {
93             mContentVisibilityEndRunnable.run();
94         }
95     }
96 
isContentVisible()97     public boolean isContentVisible() {
98         return mContentVisible;
99     }
100 
101     /**
102      * Make this view visible. If {@code false} is passed, the view will fade out it's content
103      * and set the view Visibility to GONE. If only the content should be changed
104      * {@link #setContentVisible(boolean)} can be used.
105      *
106      * @param nowVisible should the view be visible
107      * @param animate should the change be animated.
108      */
setVisible(boolean nowVisible, boolean animate)109     public void setVisible(boolean nowVisible, boolean animate) {
110         if (mIsVisible != nowVisible) {
111             mIsVisible = nowVisible;
112             if (animate) {
113                 if (nowVisible) {
114                     setVisibility(VISIBLE);
115                     setWillBeGone(false);
116                     notifyHeightChanged(false /* needsAnimation */);
117                 } else {
118                     setWillBeGone(true);
119                 }
120                 setContentVisible(nowVisible, true /* animate */);
121             } else {
122                 setVisibility(nowVisible ? VISIBLE : GONE);
123                 setContentVisible(nowVisible, false /* animate */);
124                 setWillBeGone(false);
125                 notifyHeightChanged(false /* needsAnimation */);
126             }
127         }
128     }
129 
130     /**
131      * Set the secondary view of this layout to visible.
132      *
133      * @param nowVisible should the secondary view be visible
134      * @param animate should the change be animated
135      */
setSecondaryVisible(boolean nowVisible, boolean animate)136     public void setSecondaryVisible(boolean nowVisible, boolean animate) {
137         if (mIsSecondaryVisible != nowVisible) {
138             setViewVisible(mSecondaryView, nowVisible, animate, null /* endRunnable */);
139             mIsSecondaryVisible = nowVisible;
140         }
141     }
142 
143     @VisibleForTesting
isSecondaryVisible()144     boolean isSecondaryVisible() {
145         return mIsSecondaryVisible;
146     }
147 
148     /**
149      * Is this view visible. If a view is currently animating to gone, it will
150      * return {@code false}.
151      */
isVisible()152     public boolean isVisible() {
153         return mIsVisible;
154     }
155 
setDuration(int duration)156     void setDuration(int duration) {
157         mDuration = duration;
158     }
159 
160     /**
161      * Animate a view to a new visibility.
162      * @param view Target view, maybe content view or dismiss view.
163      * @param nowVisible Should it now be visible.
164      * @param animate Should this be done in an animated way.
165      * @param endRunnable A runnable that is run when the animation is done.
166      */
setViewVisible(View view, boolean nowVisible, boolean animate, Runnable endRunnable)167     private void setViewVisible(View view, boolean nowVisible,
168             boolean animate, Runnable endRunnable) {
169         if (view == null) {
170             return;
171         }
172         // cancel any previous animations
173         view.animate().cancel();
174         float endValue = nowVisible ? 1.0f : 0.0f;
175         if (!animate) {
176             view.setAlpha(endValue);
177             if (endRunnable != null) {
178                 endRunnable.run();
179             }
180             return;
181         }
182 
183         // Animate the view alpha
184         Interpolator interpolator = nowVisible ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT;
185         view.animate()
186                 .alpha(endValue)
187                 .setInterpolator(interpolator)
188                 .setDuration(mDuration)
189                 .withEndAction(endRunnable);
190     }
191 
192     @Override
performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)193     public long performRemoveAnimation(long duration, long delay,
194             float translationDirection, boolean isHeadsUpAnimation, float endLocation,
195             Runnable onFinishedRunnable,
196             AnimatorListenerAdapter animationListener) {
197         // TODO: Use duration
198         setContentVisible(false);
199         return 0;
200     }
201 
202     @Override
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)203     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
204         // TODO: use delay and duration
205         setContentVisible(true);
206     }
207 
208     @Override
hasOverlappingRendering()209     public boolean hasOverlappingRendering() {
210         return false;
211     }
212 
findContentView()213     protected abstract View findContentView();
214 
215     /**
216      * Returns a view that might not always appear while the main content view is still visible.
217      */
findSecondaryView()218     protected abstract View findSecondaryView();
219 }
220