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
17package com.android.systemui.statusbar;
18
19import android.animation.AnimatorListenerAdapter;
20import android.content.Context;
21import android.util.AttributeSet;
22import android.view.View;
23import android.view.animation.Interpolator;
24
25import com.android.internal.annotations.VisibleForTesting;
26import 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 */
32public 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    public StackScrollerDecorView(Context context, AttributeSet attrs) {
51        super(context, attrs);
52    }
53
54    @Override
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
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
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     */
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     */
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
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     */
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     */
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
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     */
152    public boolean isVisible() {
153        return mIsVisible;
154    }
155
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     */
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
193    public void 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    }
200
201    @Override
202    public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
203        // TODO: use delay and duration
204        setContentVisible(true);
205    }
206
207    @Override
208    public boolean hasOverlappingRendering() {
209        return false;
210    }
211
212    protected abstract View findContentView();
213
214    /**
215     * Returns a view that might not always appear while the main content view is still visible.
216     */
217    protected abstract View findSecondaryView();
218}
219