blob: 2d9619927995ceb9ba251bce1e144bec797812f6 [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
Jorim Jaggibe565df2014-04-28 17:51:23 +020036
37 private 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>();
Jorim Jaggibe565df2014-04-28 17:51:23 +020044
45 public ExpandableView(Context context, AttributeSet attrs) {
46 super(context, attrs);
Selim Cinek379ff8f2015-02-20 17:03:16 +010047 mMaxViewHeight = getResources().getDimensionPixelSize(
Selim Cinek24d7cfa2014-05-20 13:50:57 +020048 R.dimen.notification_max_height);
49 }
50
51 @Override
52 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Selim Cinek379ff8f2015-02-20 17:03:16 +010053 int ownMaxHeight = mMaxViewHeight;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020054 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
55 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
56 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
57 if (hasFixedHeight || isHeightLimited) {
58 int size = MeasureSpec.getSize(heightMeasureSpec);
59 ownMaxHeight = Math.min(ownMaxHeight, size);
60 }
61 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
62 int maxChildHeight = 0;
63 int childCount = getChildCount();
64 for (int i = 0; i < childCount; i++) {
65 View child = getChildAt(i);
66 int childHeightSpec = newHeightSpec;
67 ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
68 if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
69 if (layoutParams.height >= 0) {
70 // An actual height is set
71 childHeightSpec = layoutParams.height > ownMaxHeight
72 ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
73 : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
74 }
Jorim Jaggib741f052014-06-03 19:57:26 +020075 child.measure(
76 getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
77 childHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020078 int childHeight = child.getMeasuredHeight();
79 maxChildHeight = Math.max(maxChildHeight, childHeight);
80 } else {
81 mMatchParentViews.add(child);
82 }
83 }
Selim Cinek379ff8f2015-02-20 17:03:16 +010084 int ownHeight = hasFixedHeight ? ownMaxHeight :
85 isHeightLimited ? Math.min(ownMaxHeight, maxChildHeight) : maxChildHeight;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020086 newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
87 for (View child : mMatchParentViews) {
Jorim Jaggib741f052014-06-03 19:57:26 +020088 child.measure(getChildMeasureSpec(
89 widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
90 newHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020091 }
92 mMatchParentViews.clear();
93 int width = MeasureSpec.getSize(widthMeasureSpec);
94 setMeasuredDimension(width, ownHeight);
Jorim Jaggibe565df2014-04-28 17:51:23 +020095 }
96
97 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +020098 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Selim Cinek24d7cfa2014-05-20 13:50:57 +020099 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200100 if (!mActualHeightInitialized && mActualHeight == 0) {
Selim Cinekd2319fb2014-09-01 19:41:54 +0200101 int initialHeight = getInitialHeight();
102 if (initialHeight != 0) {
103 setActualHeight(initialHeight);
104 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200105 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200106 }
107
Selim Cinek1a521f32014-11-03 17:39:29 +0100108 /**
109 * Resets the height of the view on the next layout pass
110 */
111 protected void resetActualHeight() {
Chris Wren310df312014-10-31 14:29:46 -0400112 mActualHeight = 0;
113 mActualHeightInitialized = false;
Selim Cinek1a521f32014-11-03 17:39:29 +0100114 requestLayout();
Chris Wren310df312014-10-31 14:29:46 -0400115 }
116
Selim Cinekc27437b2014-05-14 10:23:33 +0200117 protected int getInitialHeight() {
118 return getHeight();
119 }
120
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200121 @Override
122 public boolean dispatchTouchEvent(MotionEvent ev) {
123 if (filterMotionEvent(ev)) {
124 return super.dispatchTouchEvent(ev);
125 }
126 return false;
127 }
128
Selim Cinek1a521f32014-11-03 17:39:29 +0100129 protected boolean filterMotionEvent(MotionEvent event) {
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200130 return event.getActionMasked() != MotionEvent.ACTION_DOWN
131 || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
132 }
133
Jorim Jaggibe565df2014-04-28 17:51:23 +0200134 /**
135 * Sets the actual height of this notification. This is different than the laid out
136 * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200137 *
138 * @param actualHeight The height of this notification.
139 * @param notifyListeners Whether the listener should be informed about the change.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200140 */
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200141 public void setActualHeight(int actualHeight, boolean notifyListeners) {
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200142 mActualHeightInitialized = true;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200143 mActualHeight = actualHeight;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200144 if (notifyListeners) {
145 notifyHeightChanged();
146 }
147 }
148
149 public void setActualHeight(int actualHeight) {
150 setActualHeight(actualHeight, true);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200151 }
152
153 /**
154 * See {@link #setActualHeight}.
155 *
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200156 * @return The current actual height of this notification.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200157 */
158 public int getActualHeight() {
159 return mActualHeight;
160 }
161
162 /**
163 * @return The maximum height of this notification.
164 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200165 public int getMaxHeight() {
166 return getHeight();
167 }
168
169 /**
170 * @return The minimum height of this notification.
171 */
172 public int getMinHeight() {
173 return getHeight();
174 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200175
176 /**
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200177 * Sets the notification as dimmed. The default implementation does nothing.
178 *
179 * @param dimmed Whether the notification should be dimmed.
180 * @param fade Whether an animation should be played to change the state.
181 */
182 public void setDimmed(boolean dimmed, boolean fade) {
183 }
184
185 /**
John Spurlockbf370992014-06-17 13:58:31 -0400186 * Sets the notification as dark. The default implementation does nothing.
187 *
188 * @param dark Whether the notification should be dark.
189 * @param fade Whether an animation should be played to change the state.
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100190 * @param delay If fading, the delay of the animation.
John Spurlockbf370992014-06-17 13:58:31 -0400191 */
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100192 public void setDark(boolean dark, boolean fade, long delay) {
193 mDark = dark;
194 }
195
196 public boolean isDark() {
197 return mDark;
John Spurlockbf370992014-06-17 13:58:31 -0400198 }
199
200 /**
Jorim Jaggiae441282014-08-01 02:45:18 +0200201 * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
202 * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
203 * of a stack scroller update such that the updated intrinsic height (which is dependent on
204 * whether private or public layout is showing) gets taken into account into all layout
205 * calculations.
206 */
207 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
208 }
209
210 /**
211 * Sets whether the notification should hide its private contents if it is sensitive.
212 */
213 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
214 long duration) {
215 }
216
217 /**
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200218 * @return The desired notification height.
219 */
220 public int getIntrinsicHeight() {
Selim Cineka5eaa602014-05-12 21:27:47 +0200221 return getHeight();
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200222 }
223
224 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200225 * Sets the amount this view should be clipped from the top. This is used when an expanded
226 * notification is scrolling in the top or bottom stack.
227 *
228 * @param clipTopAmount The amount of pixels this view should be clipped from top.
229 */
230 public void setClipTopAmount(int clipTopAmount) {
231 mClipTopAmount = clipTopAmount;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200232 }
233
Selim Cinekeb973562014-05-02 17:07:49 +0200234 public int getClipTopAmount() {
235 return mClipTopAmount;
236 }
237
Jorim Jaggibe565df2014-04-28 17:51:23 +0200238 public void setOnHeightChangedListener(OnHeightChangedListener listener) {
239 mOnHeightChangedListener = listener;
240 }
241
242 /**
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200243 * @return Whether we can expand this views content.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200244 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200245 public boolean isContentExpandable() {
246 return false;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200247 }
248
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200249 public void notifyHeightChanged() {
250 if (mOnHeightChangedListener != null) {
251 mOnHeightChangedListener.onHeightChanged(this);
252 }
253 }
254
Selim Cinekc27437b2014-05-14 10:23:33 +0200255 public boolean isTransparent() {
256 return false;
257 }
258
Jorim Jaggibe565df2014-04-28 17:51:23 +0200259 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200260 * Perform a remove animation on this view.
261 *
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200262 * @param duration The duration of the remove animation.
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200263 * @param translationDirection The direction value from [-1 ... 1] indicating in which the
264 * animation should be performed. A value of -1 means that The
265 * remove animation should be performed upwards,
266 * such that the child appears to be going away to the top. 1
267 * Should mean the opposite.
268 * @param onFinishedRunnable A runnable which should be run when the animation is finished.
269 */
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200270 public abstract void performRemoveAnimation(long duration, float translationDirection,
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200271 Runnable onFinishedRunnable);
272
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200273 public abstract void performAddAnimation(long delay, long duration);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200274
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200275 public void setBelowSpeedBump(boolean below) {
276 }
277
Selim Cinek31094df2014-08-14 19:28:15 +0200278 public void onHeightReset() {
Selim Cineke34c6512014-08-14 11:19:41 +0200279 if (mOnHeightChangedListener != null) {
280 mOnHeightChangedListener.onReset(this);
281 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200282 }
283
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200284 /**
Selim Cineke32010a2014-08-20 23:50:41 +0200285 * This method returns the drawing rect for the view which is different from the regular
286 * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
287 * position 0 and usually the translation is neglected. Since we are manually clipping this
288 * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
289 * ensure that accessibility and focusing work correctly.
290 *
291 * @param outRect The (scrolled) drawing bounds of the view.
292 */
293 @Override
294 public void getDrawingRect(Rect outRect) {
295 super.getDrawingRect(outRect);
296 outRect.left += getTranslationX();
297 outRect.right += getTranslationX();
298 outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
299 outRect.top += getTranslationY() + getClipTopAmount();
300 }
301
302 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200303 * A listener notifying when {@link #getActualHeight} changes.
304 */
305 public interface OnHeightChangedListener {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200306
307 /**
308 * @param view the view for which the height changed, or {@code null} if just the top
309 * padding or the padding between the elements changed
310 */
Jorim Jaggibe565df2014-04-28 17:51:23 +0200311 void onHeightChanged(ExpandableView view);
Selim Cineka5e211b2014-08-11 17:35:48 +0200312
313 /**
314 * Called when the view is reset and therefore the height will change abruptly
315 *
316 * @param view The view which was reset.
317 */
318 void onReset(ExpandableView view);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200319 }
320}