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;
18 
19 import android.content.Context;
20 import android.graphics.ColorFilter;
21 import android.graphics.ColorMatrix;
22 import android.graphics.ColorMatrixColorFilter;
23 import android.graphics.Paint;
24 import android.graphics.PorterDuff;
25 import android.graphics.PorterDuffXfermode;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.util.AttributeSet;
29 import android.view.View;
30 import android.view.ViewTreeObserver;
31 import android.view.animation.Interpolator;
32 import android.view.animation.LinearInterpolator;
33 import android.widget.FrameLayout;
34 import android.widget.ImageView;
35 import com.android.systemui.R;
36 
37 /**
38  * A frame layout containing the actual payload of the notification, including the contracted and
39  * expanded layout. This class is responsible for clipping the content and and switching between the
40  * expanded and contracted view depending on its clipped size.
41  */
42 public class NotificationContentView extends FrameLayout {
43 
44     private static final long ANIMATION_DURATION_LENGTH = 170;
45 
46     private final Rect mClipBounds = new Rect();
47 
48     private View mContractedChild;
49     private View mExpandedChild;
50 
51     private NotificationViewWrapper mContractedWrapper;
52 
53     private int mSmallHeight;
54     private int mClipTopAmount;
55     private int mActualHeight;
56 
57     private final Interpolator mLinearInterpolator = new LinearInterpolator();
58 
59     private boolean mContractedVisible = true;
60     private boolean mDark;
61 
62     private final Paint mFadePaint = new Paint();
63     private boolean mAnimate;
64     private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
65             = new ViewTreeObserver.OnPreDrawListener() {
66         @Override
67         public boolean onPreDraw() {
68             mAnimate = true;
69             getViewTreeObserver().removeOnPreDrawListener(this);
70             return true;
71         }
72     };
73 
NotificationContentView(Context context, AttributeSet attrs)74     public NotificationContentView(Context context, AttributeSet attrs) {
75         super(context, attrs);
76         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
77         reset(true);
78     }
79 
80     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)81     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
82         super.onLayout(changed, left, top, right, bottom);
83         updateClipping();
84     }
85 
86     @Override
onAttachedToWindow()87     protected void onAttachedToWindow() {
88         super.onAttachedToWindow();
89         updateVisibility();
90     }
91 
reset(boolean resetActualHeight)92     public void reset(boolean resetActualHeight) {
93         if (mContractedChild != null) {
94             mContractedChild.animate().cancel();
95         }
96         if (mExpandedChild != null) {
97             mExpandedChild.animate().cancel();
98         }
99         removeAllViews();
100         mContractedChild = null;
101         mExpandedChild = null;
102         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
103         mContractedVisible = true;
104         if (resetActualHeight) {
105             mActualHeight = mSmallHeight;
106         }
107     }
108 
getContractedChild()109     public View getContractedChild() {
110         return mContractedChild;
111     }
112 
getExpandedChild()113     public View getExpandedChild() {
114         return mExpandedChild;
115     }
116 
setContractedChild(View child)117     public void setContractedChild(View child) {
118         if (mContractedChild != null) {
119             mContractedChild.animate().cancel();
120             removeView(mContractedChild);
121         }
122         sanitizeContractedLayoutParams(child);
123         addView(child);
124         mContractedChild = child;
125         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
126         selectLayout(false /* animate */, true /* force */);
127         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
128     }
129 
setExpandedChild(View child)130     public void setExpandedChild(View child) {
131         if (mExpandedChild != null) {
132             mExpandedChild.animate().cancel();
133             removeView(mExpandedChild);
134         }
135         addView(child);
136         mExpandedChild = child;
137         selectLayout(false /* animate */, true /* force */);
138     }
139 
140     @Override
onVisibilityChanged(View changedView, int visibility)141     protected void onVisibilityChanged(View changedView, int visibility) {
142         super.onVisibilityChanged(changedView, visibility);
143         updateVisibility();
144     }
145 
updateVisibility()146     private void updateVisibility() {
147         setVisible(isShown());
148     }
149 
setVisible(final boolean isVisible)150     private void setVisible(final boolean isVisible) {
151         if (isVisible) {
152 
153             // We only animate if we are drawn at least once, otherwise the view might animate when
154             // it's shown the first time
155             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
156         } else {
157             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
158             mAnimate = false;
159         }
160     }
161 
setActualHeight(int actualHeight)162     public void setActualHeight(int actualHeight) {
163         mActualHeight = actualHeight;
164         selectLayout(mAnimate /* animate */, false /* force */);
165         updateClipping();
166     }
167 
getMaxHeight()168     public int getMaxHeight() {
169 
170         // The maximum height is just the laid out height.
171         return getHeight();
172     }
173 
getMinHeight()174     public int getMinHeight() {
175         return mSmallHeight;
176     }
177 
setClipTopAmount(int clipTopAmount)178     public void setClipTopAmount(int clipTopAmount) {
179         mClipTopAmount = clipTopAmount;
180         updateClipping();
181     }
182 
updateClipping()183     private void updateClipping() {
184         mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
185         setClipBounds(mClipBounds);
186     }
187 
sanitizeContractedLayoutParams(View contractedChild)188     private void sanitizeContractedLayoutParams(View contractedChild) {
189         LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
190         lp.height = mSmallHeight;
191         contractedChild.setLayoutParams(lp);
192     }
193 
selectLayout(boolean animate, boolean force)194     private void selectLayout(boolean animate, boolean force) {
195         if (mContractedChild == null) {
196             return;
197         }
198         boolean showContractedChild = showContractedChild();
199         if (showContractedChild != mContractedVisible || force) {
200             if (animate && mExpandedChild != null) {
201                 runSwitchAnimation(showContractedChild);
202             } else if (mExpandedChild != null) {
203                 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
204                 mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
205                 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
206                 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
207             }
208         }
209         mContractedVisible = showContractedChild;
210     }
211 
runSwitchAnimation(final boolean showContractedChild)212     private void runSwitchAnimation(final boolean showContractedChild) {
213         mContractedChild.setVisibility(View.VISIBLE);
214         mExpandedChild.setVisibility(View.VISIBLE);
215         mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
216         mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
217         setLayerType(LAYER_TYPE_HARDWARE, null);
218         mContractedChild.animate()
219                 .alpha(showContractedChild ? 1f : 0f)
220                 .setDuration(ANIMATION_DURATION_LENGTH)
221                 .setInterpolator(mLinearInterpolator);
222         mExpandedChild.animate()
223                 .alpha(showContractedChild ? 0f : 1f)
224                 .setDuration(ANIMATION_DURATION_LENGTH)
225                 .setInterpolator(mLinearInterpolator)
226                 .withEndAction(new Runnable() {
227                     @Override
228                     public void run() {
229                         mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
230                         mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
231                         setLayerType(LAYER_TYPE_NONE, null);
232                         mContractedChild.setVisibility(showContractedChild
233                                 ? View.VISIBLE
234                                 : View.INVISIBLE);
235                         mExpandedChild.setVisibility(showContractedChild
236                                 ? View.INVISIBLE
237                                 : View.VISIBLE);
238                     }
239                 });
240     }
241 
showContractedChild()242     private boolean showContractedChild() {
243         return mActualHeight <= mSmallHeight || mExpandedChild == null;
244     }
245 
notifyContentUpdated()246     public void notifyContentUpdated() {
247         selectLayout(false /* animate */, true /* force */);
248         if (mContractedChild != null) {
249             mContractedWrapper.notifyContentUpdated();
250             mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
251         }
252     }
253 
isContentExpandable()254     public boolean isContentExpandable() {
255         return mExpandedChild != null;
256     }
257 
setDark(boolean dark, boolean fade, long delay)258     public void setDark(boolean dark, boolean fade, long delay) {
259         if (mDark == dark || mContractedChild == null) return;
260         mDark = dark;
261         mContractedWrapper.setDark(dark, fade, delay);
262     }
263 
264     @Override
hasOverlappingRendering()265     public boolean hasOverlappingRendering() {
266 
267         // This is not really true, but good enough when fading from the contracted to the expanded
268         // layout, and saves us some layers.
269         return false;
270     }
271 }
272