blob: 7ae0d6dd91af45465a0ee5e46abc6eeefee5cb62 [file] [log] [blame]
Jorim Jaggibe565df2014-04-28 17:51:23 +02001/*
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.content.Context;
Selim Cineke32010a2014-08-20 23:50:41 +020020import android.graphics.Rect;
Jorim Jaggibe565df2014-04-28 17:51:23 +020021import android.util.AttributeSet;
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +020022import android.view.MotionEvent;
Jorim Jaggibe565df2014-04-28 17:51:23 +020023import android.view.View;
Selim Cinekc9c00ae2014-05-20 03:33:40 +020024import android.view.ViewGroup;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020025import android.widget.FrameLayout;
26import com.android.systemui.R;
Selim Cineke32010a2014-08-20 23:50:41 +020027import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020028
29import java.util.ArrayList;
Jorim Jaggibe565df2014-04-28 17:51:23 +020030
31/**
32 * An abstract view for expandable views.
33 */
Selim Cinek24d7cfa2014-05-20 13:50:57 +020034public abstract class ExpandableView extends FrameLayout {
35
Selim Cinekb5605e52015-02-20 18:21:41 +010036 private final int mBottomDecorHeight;
37 protected OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek379ff8f2015-02-20 17:03:16 +010038 protected int mMaxViewHeight;
Chris Wren310df312014-10-31 14:29:46 -040039 private int mActualHeight;
Jorim Jaggibe565df2014-04-28 17:51:23 +020040 protected int mClipTopAmount;
Jorim Jaggibe565df2014-04-28 17:51:23 +020041 private boolean mActualHeightInitialized;
Jorim Jaggi4e857f42014-11-17 19:14:04 +010042 private boolean mDark;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020043 private ArrayList<View> mMatchParentViews = new ArrayList<View>();
Selim Cineka272dfe2015-02-20 18:12:28 +010044 private int mClipTopOptimization;
45 private static Rect mClipRect = new Rect();
Jorim Jaggibe565df2014-04-28 17:51:23 +020046
47 public ExpandableView(Context context, AttributeSet attrs) {
48 super(context, attrs);
Selim Cinek379ff8f2015-02-20 17:03:16 +010049 mMaxViewHeight = getResources().getDimensionPixelSize(
Selim Cinek24d7cfa2014-05-20 13:50:57 +020050 R.dimen.notification_max_height);
Selim Cinekb5605e52015-02-20 18:21:41 +010051 mBottomDecorHeight = resolveBottomDecorHeight();
52 }
53
54 protected int resolveBottomDecorHeight() {
55 return getResources().getDimensionPixelSize(
56 R.dimen.notification_bottom_decor_height);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020057 }
58
59 @Override
60 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Selim Cinek379ff8f2015-02-20 17:03:16 +010061 int ownMaxHeight = mMaxViewHeight;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020062 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
63 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
64 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
65 if (hasFixedHeight || isHeightLimited) {
66 int size = MeasureSpec.getSize(heightMeasureSpec);
67 ownMaxHeight = Math.min(ownMaxHeight, size);
68 }
69 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
70 int maxChildHeight = 0;
71 int childCount = getChildCount();
72 for (int i = 0; i < childCount; i++) {
73 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +010074 if (child.getVisibility() == GONE || isChildInvisible(child)) {
75 continue;
76 }
Selim Cinek24d7cfa2014-05-20 13:50:57 +020077 int childHeightSpec = newHeightSpec;
78 ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
79 if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
80 if (layoutParams.height >= 0) {
81 // An actual height is set
82 childHeightSpec = layoutParams.height > ownMaxHeight
83 ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
84 : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
85 }
Jorim Jaggib741f052014-06-03 19:57:26 +020086 child.measure(
87 getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
88 childHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020089 int childHeight = child.getMeasuredHeight();
90 maxChildHeight = Math.max(maxChildHeight, childHeight);
91 } else {
92 mMatchParentViews.add(child);
93 }
94 }
Selim Cinek379ff8f2015-02-20 17:03:16 +010095 int ownHeight = hasFixedHeight ? ownMaxHeight :
96 isHeightLimited ? Math.min(ownMaxHeight, maxChildHeight) : maxChildHeight;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020097 newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
98 for (View child : mMatchParentViews) {
Jorim Jaggib741f052014-06-03 19:57:26 +020099 child.measure(getChildMeasureSpec(
100 widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
101 newHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200102 }
103 mMatchParentViews.clear();
104 int width = MeasureSpec.getSize(widthMeasureSpec);
Selim Cinekb5605e52015-02-20 18:21:41 +0100105 if (canHaveBottomDecor()) {
106 // We always account for the expandAction as well.
107 ownHeight += mBottomDecorHeight;
108 }
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200109 setMeasuredDimension(width, ownHeight);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200110 }
111
112 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200113 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200114 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200115 if (!mActualHeightInitialized && mActualHeight == 0) {
Selim Cinekd2319fb2014-09-01 19:41:54 +0200116 int initialHeight = getInitialHeight();
117 if (initialHeight != 0) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100118 setContentHeight(initialHeight);
Selim Cinekd2319fb2014-09-01 19:41:54 +0200119 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200120 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200121 }
122
Selim Cinek1a521f32014-11-03 17:39:29 +0100123 /**
124 * Resets the height of the view on the next layout pass
125 */
126 protected void resetActualHeight() {
Chris Wren310df312014-10-31 14:29:46 -0400127 mActualHeight = 0;
128 mActualHeightInitialized = false;
Selim Cinek1a521f32014-11-03 17:39:29 +0100129 requestLayout();
Chris Wren310df312014-10-31 14:29:46 -0400130 }
131
Selim Cinekc27437b2014-05-14 10:23:33 +0200132 protected int getInitialHeight() {
133 return getHeight();
134 }
135
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200136 @Override
137 public boolean dispatchTouchEvent(MotionEvent ev) {
138 if (filterMotionEvent(ev)) {
139 return super.dispatchTouchEvent(ev);
140 }
141 return false;
142 }
143
Selim Cinek1a521f32014-11-03 17:39:29 +0100144 protected boolean filterMotionEvent(MotionEvent event) {
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200145 return event.getActionMasked() != MotionEvent.ACTION_DOWN
146 || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
147 }
148
Jorim Jaggibe565df2014-04-28 17:51:23 +0200149 /**
150 * Sets the actual height of this notification. This is different than the laid out
151 * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200152 *
153 * @param actualHeight The height of this notification.
154 * @param notifyListeners Whether the listener should be informed about the change.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200155 */
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200156 public void setActualHeight(int actualHeight, boolean notifyListeners) {
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200157 mActualHeightInitialized = true;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200158 mActualHeight = actualHeight;
Selim Cineka272dfe2015-02-20 18:12:28 +0100159 updateClipping();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200160 if (notifyListeners) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100161 notifyHeightChanged(false /* needsAnimation */);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200162 }
163 }
164
Selim Cinekb5605e52015-02-20 18:21:41 +0100165 public void setContentHeight(int contentHeight) {
166 setActualHeight(contentHeight + getBottomDecorHeight(), true);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200167 }
168
169 /**
170 * See {@link #setActualHeight}.
171 *
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200172 * @return The current actual height of this notification.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200173 */
174 public int getActualHeight() {
175 return mActualHeight;
176 }
177
178 /**
Selim Cinekb5605e52015-02-20 18:21:41 +0100179 * This view may have a bottom decor which will be placed below the content. If it has one, this
180 * view will be layouted higher than just the content by {@link #mBottomDecorHeight}.
181 * @return the height of the decor if it currently has one
182 */
183 public int getBottomDecorHeight() {
184 return hasBottomDecor() ? mBottomDecorHeight : 0;
185 }
186
187 /**
188 * @return whether this view may have a bottom decor at all. This will force the view to layout
189 * itself higher than just it's content
190 */
191 protected boolean canHaveBottomDecor() {
192 return false;
193 }
194
195 /**
196 * @return whether this view has a decor view below it's content. This will make the intrinsic
197 * height from {@link #getIntrinsicHeight()} higher as well
198 */
199 protected boolean hasBottomDecor() {
200 return false;
201 }
202
203 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200204 * @return The maximum height of this notification.
205 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100206 public int getMaxContentHeight() {
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200207 return getHeight();
208 }
209
210 /**
Selim Cinekb5605e52015-02-20 18:21:41 +0100211 * @return The minimum content height of this notification.
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200212 */
213 public int getMinHeight() {
214 return getHeight();
215 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200216
217 /**
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200218 * Sets the notification as dimmed. The default implementation does nothing.
219 *
220 * @param dimmed Whether the notification should be dimmed.
221 * @param fade Whether an animation should be played to change the state.
222 */
223 public void setDimmed(boolean dimmed, boolean fade) {
224 }
225
226 /**
John Spurlockbf370992014-06-17 13:58:31 -0400227 * Sets the notification as dark. The default implementation does nothing.
228 *
229 * @param dark Whether the notification should be dark.
230 * @param fade Whether an animation should be played to change the state.
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100231 * @param delay If fading, the delay of the animation.
John Spurlockbf370992014-06-17 13:58:31 -0400232 */
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100233 public void setDark(boolean dark, boolean fade, long delay) {
234 mDark = dark;
235 }
236
237 public boolean isDark() {
238 return mDark;
John Spurlockbf370992014-06-17 13:58:31 -0400239 }
240
241 /**
Jorim Jaggiae441282014-08-01 02:45:18 +0200242 * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
243 * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
244 * of a stack scroller update such that the updated intrinsic height (which is dependent on
245 * whether private or public layout is showing) gets taken into account into all layout
246 * calculations.
247 */
248 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
249 }
250
251 /**
252 * Sets whether the notification should hide its private contents if it is sensitive.
253 */
254 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
255 long duration) {
256 }
257
258 /**
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200259 * @return The desired notification height.
260 */
261 public int getIntrinsicHeight() {
Selim Cineka5eaa602014-05-12 21:27:47 +0200262 return getHeight();
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200263 }
264
265 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200266 * Sets the amount this view should be clipped from the top. This is used when an expanded
267 * notification is scrolling in the top or bottom stack.
268 *
269 * @param clipTopAmount The amount of pixels this view should be clipped from top.
270 */
271 public void setClipTopAmount(int clipTopAmount) {
272 mClipTopAmount = clipTopAmount;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200273 }
274
Selim Cinekeb973562014-05-02 17:07:49 +0200275 public int getClipTopAmount() {
276 return mClipTopAmount;
277 }
278
Jorim Jaggibe565df2014-04-28 17:51:23 +0200279 public void setOnHeightChangedListener(OnHeightChangedListener listener) {
280 mOnHeightChangedListener = listener;
281 }
282
283 /**
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200284 * @return Whether we can expand this views content.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200285 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200286 public boolean isContentExpandable() {
287 return false;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200288 }
289
Selim Cinekb5605e52015-02-20 18:21:41 +0100290 public void notifyHeightChanged(boolean needsAnimation) {
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200291 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100292 mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200293 }
294 }
295
Selim Cinekc27437b2014-05-14 10:23:33 +0200296 public boolean isTransparent() {
297 return false;
298 }
299
Jorim Jaggibe565df2014-04-28 17:51:23 +0200300 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200301 * Perform a remove animation on this view.
302 *
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200303 * @param duration The duration of the remove animation.
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200304 * @param translationDirection The direction value from [-1 ... 1] indicating in which the
305 * animation should be performed. A value of -1 means that The
306 * remove animation should be performed upwards,
307 * such that the child appears to be going away to the top. 1
308 * Should mean the opposite.
309 * @param onFinishedRunnable A runnable which should be run when the animation is finished.
310 */
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200311 public abstract void performRemoveAnimation(long duration, float translationDirection,
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200312 Runnable onFinishedRunnable);
313
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200314 public abstract void performAddAnimation(long delay, long duration);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200315
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200316 public void setBelowSpeedBump(boolean below) {
317 }
318
Selim Cinek31094df2014-08-14 19:28:15 +0200319 public void onHeightReset() {
Selim Cineke34c6512014-08-14 11:19:41 +0200320 if (mOnHeightChangedListener != null) {
321 mOnHeightChangedListener.onReset(this);
322 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200323 }
324
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200325 /**
Selim Cineke32010a2014-08-20 23:50:41 +0200326 * This method returns the drawing rect for the view which is different from the regular
327 * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
328 * position 0 and usually the translation is neglected. Since we are manually clipping this
329 * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
330 * ensure that accessibility and focusing work correctly.
331 *
332 * @param outRect The (scrolled) drawing bounds of the view.
333 */
334 @Override
335 public void getDrawingRect(Rect outRect) {
336 super.getDrawingRect(outRect);
337 outRect.left += getTranslationX();
338 outRect.right += getTranslationX();
339 outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
340 outRect.top += getTranslationY() + getClipTopAmount();
341 }
342
Selim Cinekb5605e52015-02-20 18:21:41 +0100343 public int getContentHeight() {
344 return mActualHeight - getBottomDecorHeight();
345 }
346
347 /**
348 * @return whether the given child can be ignored for layouting and measuring purposes
349 */
350 protected boolean isChildInvisible(View child) {
351 return false;
352 }
353
354 public boolean areChildrenExpanded() {
355 return false;
356 }
357
Selim Cineka272dfe2015-02-20 18:12:28 +0100358 private void updateClipping() {
359 mClipRect.set(0, mClipTopOptimization, getWidth(), getActualHeight());
360 setClipBounds(mClipRect);
361 }
362
363 public int getClipTopOptimization() {
364 return mClipTopOptimization;
365 }
366
367 /**
368 * Set that the view will be clipped by a given amount from the top. Contrary to
369 * {@link #setClipTopAmount} this amount doesn't effect shadows and the background.
370 *
371 * @param clipTopOptimization the amount to clip from the top
372 */
373 public void setClipTopOptimization(int clipTopOptimization) {
374 mClipTopOptimization = clipTopOptimization;
375 updateClipping();
376 }
377
Selim Cineke32010a2014-08-20 23:50:41 +0200378 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200379 * A listener notifying when {@link #getActualHeight} changes.
380 */
381 public interface OnHeightChangedListener {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200382
383 /**
384 * @param view the view for which the height changed, or {@code null} if just the top
385 * padding or the padding between the elements changed
Selim Cinekb5605e52015-02-20 18:21:41 +0100386 * @param needsAnimation whether the view height needs to be animated
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200387 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100388 void onHeightChanged(ExpandableView view, boolean needsAnimation);
Selim Cineka5e211b2014-08-11 17:35:48 +0200389
390 /**
391 * Called when the view is reset and therefore the height will change abruptly
392 *
393 * @param view The view which was reset.
394 */
395 void onReset(ExpandableView view);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200396 }
397}