• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.animation.Interpolator;
31 import android.view.animation.LinearInterpolator;
32 import android.widget.FrameLayout;
33 import android.widget.ImageView;
34 
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     private static final Paint INVERT_PAINT = createInvertPaint();
46     private static final ColorFilter NO_COLOR_FILTER = new ColorFilter();
47 
48     private final Rect mClipBounds = new Rect();
49 
50     private View mContractedChild;
51     private View mExpandedChild;
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 
NotificationContentView(Context context, AttributeSet attrs)64     public NotificationContentView(Context context, AttributeSet attrs) {
65         super(context, attrs);
66         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
67         reset();
68     }
69 
70     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)71     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
72         super.onLayout(changed, left, top, right, bottom);
73         updateClipping();
74     }
75 
reset()76     public void reset() {
77         if (mContractedChild != null) {
78             mContractedChild.animate().cancel();
79         }
80         if (mExpandedChild != null) {
81             mExpandedChild.animate().cancel();
82         }
83         removeAllViews();
84         mContractedChild = null;
85         mExpandedChild = null;
86         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
87         mActualHeight = mSmallHeight;
88         mContractedVisible = true;
89     }
90 
getContractedChild()91     public View getContractedChild() {
92         return mContractedChild;
93     }
94 
getExpandedChild()95     public View getExpandedChild() {
96         return mExpandedChild;
97     }
98 
setContractedChild(View child)99     public void setContractedChild(View child) {
100         if (mContractedChild != null) {
101             mContractedChild.animate().cancel();
102             removeView(mContractedChild);
103         }
104         sanitizeContractedLayoutParams(child);
105         addView(child);
106         mContractedChild = child;
107         selectLayout(false /* animate */, true /* force */);
108     }
109 
setExpandedChild(View child)110     public void setExpandedChild(View child) {
111         if (mExpandedChild != null) {
112             mExpandedChild.animate().cancel();
113             removeView(mExpandedChild);
114         }
115         addView(child);
116         mExpandedChild = child;
117         selectLayout(false /* animate */, true /* force */);
118     }
119 
setActualHeight(int actualHeight)120     public void setActualHeight(int actualHeight) {
121         mActualHeight = actualHeight;
122         selectLayout(true /* animate */, false /* force */);
123         updateClipping();
124     }
125 
getMaxHeight()126     public int getMaxHeight() {
127 
128         // The maximum height is just the laid out height.
129         return getHeight();
130     }
131 
getMinHeight()132     public int getMinHeight() {
133         return mSmallHeight;
134     }
135 
setClipTopAmount(int clipTopAmount)136     public void setClipTopAmount(int clipTopAmount) {
137         mClipTopAmount = clipTopAmount;
138         updateClipping();
139     }
140 
updateClipping()141     private void updateClipping() {
142         mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
143         setClipBounds(mClipBounds);
144     }
145 
sanitizeContractedLayoutParams(View contractedChild)146     private void sanitizeContractedLayoutParams(View contractedChild) {
147         LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
148         lp.height = mSmallHeight;
149         contractedChild.setLayoutParams(lp);
150     }
151 
selectLayout(boolean animate, boolean force)152     private void selectLayout(boolean animate, boolean force) {
153         if (mContractedChild == null) {
154             return;
155         }
156         boolean showContractedChild = showContractedChild();
157         if (showContractedChild != mContractedVisible || force) {
158             if (animate && mExpandedChild != null) {
159                 runSwitchAnimation(showContractedChild);
160             } else if (mExpandedChild != null) {
161                 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
162                 mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
163                 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
164                 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
165             }
166         }
167         mContractedVisible = showContractedChild;
168     }
169 
runSwitchAnimation(final boolean showContractedChild)170     private void runSwitchAnimation(final boolean showContractedChild) {
171         mContractedChild.setVisibility(View.VISIBLE);
172         mExpandedChild.setVisibility(View.VISIBLE);
173         mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
174         mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
175         setLayerType(LAYER_TYPE_HARDWARE, null);
176         mContractedChild.animate()
177                 .alpha(showContractedChild ? 1f : 0f)
178                 .setDuration(ANIMATION_DURATION_LENGTH)
179                 .setInterpolator(mLinearInterpolator);
180         mExpandedChild.animate()
181                 .alpha(showContractedChild ? 0f : 1f)
182                 .setDuration(ANIMATION_DURATION_LENGTH)
183                 .setInterpolator(mLinearInterpolator)
184                 .withEndAction(new Runnable() {
185                     @Override
186                     public void run() {
187                         mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
188                         mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
189                         setLayerType(LAYER_TYPE_NONE, null);
190                         mContractedChild.setVisibility(showContractedChild
191                                 ? View.VISIBLE
192                                 : View.INVISIBLE);
193                         mExpandedChild.setVisibility(showContractedChild
194                                 ? View.INVISIBLE
195                                 : View.VISIBLE);
196                     }
197                 });
198     }
199 
showContractedChild()200     private boolean showContractedChild() {
201         return mActualHeight <= mSmallHeight || mExpandedChild == null;
202     }
203 
notifyContentUpdated()204     public void notifyContentUpdated() {
205         selectLayout(false /* animate */, true /* force */);
206     }
207 
isContentExpandable()208     public boolean isContentExpandable() {
209         return mExpandedChild != null;
210     }
211 
setDark(boolean dark, boolean fade)212     public void setDark(boolean dark, boolean fade) {
213         if (mDark == dark || mContractedChild == null) return;
214         mDark = dark;
215         setImageViewDark(dark, fade, com.android.internal.R.id.right_icon);
216         setImageViewDark(dark, fade, com.android.internal.R.id.icon);
217     }
218 
setImageViewDark(boolean dark, boolean fade, int imageViewId)219     private void setImageViewDark(boolean dark, boolean fade, int imageViewId) {
220         // TODO: implement fade
221         final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId);
222         if (v == null) return;
223         final Drawable d = v.getBackground();
224         if (dark) {
225             v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT);
226             if (d != null) {
227                 v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter()
228                         : NO_COLOR_FILTER);
229                 d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color),
230                         PorterDuff.Mode.SRC_ATOP);
231                 v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha));
232             }
233         } else {
234             v.setLayerType(LAYER_TYPE_NONE, null);
235             if (d != null)  {
236                 final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag);
237                 if (filter != null) {
238                     d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter);
239                     v.setTag(R.id.doze_saved_filter_tag, null);
240                 }
241                 v.setImageAlpha(0xff);
242             }
243         }
244     }
245 
246     @Override
hasOverlappingRendering()247     public boolean hasOverlappingRendering() {
248 
249         // This is not really true, but good enough when fading from the contracted to the expanded
250         // layout, and saves us some layers.
251         return false;
252     }
253 
createInvertPaint()254     private static Paint createInvertPaint() {
255         final Paint p = new Paint();
256         final float[] invert = {
257             -1f,  0f,  0f, 1f, 1f,
258              0f, -1f,  0f, 1f, 1f,
259              0f,  0f, -1f, 1f, 1f,
260              0f,  0f,  0f, 1f, 0f
261         };
262         p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert)));
263         return p;
264     }
265 }
266