blob: 28387479b13a5faddc7dac2034be0e5716cec8f9 [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
36 private final int mMaxNotificationHeight;
Jorim Jaggibe565df2014-04-28 17:51:23 +020037
38 private OnHeightChangedListener mOnHeightChangedListener;
39 protected int mActualHeight;
40 protected int mClipTopAmount;
Jorim Jaggibe565df2014-04-28 17:51:23 +020041 private boolean mActualHeightInitialized;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020042 private ArrayList<View> mMatchParentViews = new ArrayList<View>();
Jorim Jaggibe565df2014-04-28 17:51:23 +020043
44 public ExpandableView(Context context, AttributeSet attrs) {
45 super(context, attrs);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020046 mMaxNotificationHeight = getResources().getDimensionPixelSize(
47 R.dimen.notification_max_height);
48 }
49
50 @Override
51 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
52 int ownMaxHeight = mMaxNotificationHeight;
53 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
54 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
55 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
56 if (hasFixedHeight || isHeightLimited) {
57 int size = MeasureSpec.getSize(heightMeasureSpec);
58 ownMaxHeight = Math.min(ownMaxHeight, size);
59 }
60 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
61 int maxChildHeight = 0;
62 int childCount = getChildCount();
63 for (int i = 0; i < childCount; i++) {
64 View child = getChildAt(i);
65 int childHeightSpec = newHeightSpec;
66 ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
67 if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
68 if (layoutParams.height >= 0) {
69 // An actual height is set
70 childHeightSpec = layoutParams.height > ownMaxHeight
71 ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
72 : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
73 }
Jorim Jaggib741f052014-06-03 19:57:26 +020074 child.measure(
75 getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
76 childHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020077 int childHeight = child.getMeasuredHeight();
78 maxChildHeight = Math.max(maxChildHeight, childHeight);
79 } else {
80 mMatchParentViews.add(child);
81 }
82 }
83 int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
84 newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
85 for (View child : mMatchParentViews) {
Jorim Jaggib741f052014-06-03 19:57:26 +020086 child.measure(getChildMeasureSpec(
87 widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
88 newHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +020089 }
90 mMatchParentViews.clear();
91 int width = MeasureSpec.getSize(widthMeasureSpec);
92 setMeasuredDimension(width, ownHeight);
Jorim Jaggibe565df2014-04-28 17:51:23 +020093 }
94
95 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +020096 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Selim Cinek24d7cfa2014-05-20 13:50:57 +020097 super.onLayout(changed, left, top, right, bottom);
Jorim Jaggibe565df2014-04-28 17:51:23 +020098 if (!mActualHeightInitialized && mActualHeight == 0) {
Selim Cinekd2319fb2014-09-01 19:41:54 +020099 int initialHeight = getInitialHeight();
100 if (initialHeight != 0) {
101 setActualHeight(initialHeight);
102 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200103 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200104 }
105
Selim Cinekc27437b2014-05-14 10:23:33 +0200106 protected int getInitialHeight() {
107 return getHeight();
108 }
109
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200110 @Override
111 public boolean dispatchTouchEvent(MotionEvent ev) {
112 if (filterMotionEvent(ev)) {
113 return super.dispatchTouchEvent(ev);
114 }
115 return false;
116 }
117
118 private boolean filterMotionEvent(MotionEvent event) {
119 return event.getActionMasked() != MotionEvent.ACTION_DOWN
120 || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
121 }
122
Jorim Jaggibe565df2014-04-28 17:51:23 +0200123 /**
124 * Sets the actual height of this notification. This is different than the laid out
125 * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200126 *
127 * @param actualHeight The height of this notification.
128 * @param notifyListeners Whether the listener should be informed about the change.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200129 */
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200130 public void setActualHeight(int actualHeight, boolean notifyListeners) {
Jorim Jaggi2580a9762014-06-25 03:08:25 +0200131 mActualHeightInitialized = true;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200132 mActualHeight = actualHeight;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200133 if (notifyListeners) {
134 notifyHeightChanged();
135 }
136 }
137
138 public void setActualHeight(int actualHeight) {
139 setActualHeight(actualHeight, true);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200140 }
141
142 /**
143 * See {@link #setActualHeight}.
144 *
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200145 * @return The current actual height of this notification.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200146 */
147 public int getActualHeight() {
148 return mActualHeight;
149 }
150
151 /**
152 * @return The maximum height of this notification.
153 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200154 public int getMaxHeight() {
155 return getHeight();
156 }
157
158 /**
159 * @return The minimum height of this notification.
160 */
161 public int getMinHeight() {
162 return getHeight();
163 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200164
165 /**
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200166 * Sets the notification as dimmed. The default implementation does nothing.
167 *
168 * @param dimmed Whether the notification should be dimmed.
169 * @param fade Whether an animation should be played to change the state.
170 */
171 public void setDimmed(boolean dimmed, boolean fade) {
172 }
173
174 /**
John Spurlockbf370992014-06-17 13:58:31 -0400175 * Sets the notification as dark. The default implementation does nothing.
176 *
177 * @param dark Whether the notification should be dark.
178 * @param fade Whether an animation should be played to change the state.
179 */
180 public void setDark(boolean dark, boolean fade) {
181 }
182
183 /**
Jorim Jaggiae441282014-08-01 02:45:18 +0200184 * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
185 * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
186 * of a stack scroller update such that the updated intrinsic height (which is dependent on
187 * whether private or public layout is showing) gets taken into account into all layout
188 * calculations.
189 */
190 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
191 }
192
193 /**
194 * Sets whether the notification should hide its private contents if it is sensitive.
195 */
196 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
197 long duration) {
198 }
199
200 /**
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200201 * @return The desired notification height.
202 */
203 public int getIntrinsicHeight() {
Selim Cineka5eaa602014-05-12 21:27:47 +0200204 return getHeight();
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200205 }
206
207 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200208 * Sets the amount this view should be clipped from the top. This is used when an expanded
209 * notification is scrolling in the top or bottom stack.
210 *
211 * @param clipTopAmount The amount of pixels this view should be clipped from top.
212 */
213 public void setClipTopAmount(int clipTopAmount) {
214 mClipTopAmount = clipTopAmount;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200215 }
216
Selim Cinekeb973562014-05-02 17:07:49 +0200217 public int getClipTopAmount() {
218 return mClipTopAmount;
219 }
220
Jorim Jaggibe565df2014-04-28 17:51:23 +0200221 public void setOnHeightChangedListener(OnHeightChangedListener listener) {
222 mOnHeightChangedListener = listener;
223 }
224
225 /**
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200226 * @return Whether we can expand this views content.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200227 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200228 public boolean isContentExpandable() {
229 return false;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200230 }
231
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200232 public void notifyHeightChanged() {
233 if (mOnHeightChangedListener != null) {
234 mOnHeightChangedListener.onHeightChanged(this);
235 }
236 }
237
Selim Cinekc27437b2014-05-14 10:23:33 +0200238 public boolean isTransparent() {
239 return false;
240 }
241
Jorim Jaggibe565df2014-04-28 17:51:23 +0200242 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200243 * Perform a remove animation on this view.
244 *
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200245 * @param duration The duration of the remove animation.
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200246 * @param translationDirection The direction value from [-1 ... 1] indicating in which the
247 * animation should be performed. A value of -1 means that The
248 * remove animation should be performed upwards,
249 * such that the child appears to be going away to the top. 1
250 * Should mean the opposite.
251 * @param onFinishedRunnable A runnable which should be run when the animation is finished.
252 */
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200253 public abstract void performRemoveAnimation(long duration, float translationDirection,
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200254 Runnable onFinishedRunnable);
255
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200256 public abstract void performAddAnimation(long delay, long duration);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200257
Selim Cinekf54090e2014-06-17 17:24:51 -0700258 public abstract void setScrimAmount(float scrimAmount);
259
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200260 public void setBelowSpeedBump(boolean below) {
261 }
262
Selim Cinek31094df2014-08-14 19:28:15 +0200263 public void onHeightReset() {
Selim Cineke34c6512014-08-14 11:19:41 +0200264 if (mOnHeightChangedListener != null) {
265 mOnHeightChangedListener.onReset(this);
266 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200267 }
268
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200269 /**
Selim Cineke32010a2014-08-20 23:50:41 +0200270 * This method returns the drawing rect for the view which is different from the regular
271 * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
272 * position 0 and usually the translation is neglected. Since we are manually clipping this
273 * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
274 * ensure that accessibility and focusing work correctly.
275 *
276 * @param outRect The (scrolled) drawing bounds of the view.
277 */
278 @Override
279 public void getDrawingRect(Rect outRect) {
280 super.getDrawingRect(outRect);
281 outRect.left += getTranslationX();
282 outRect.right += getTranslationX();
283 outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
284 outRect.top += getTranslationY() + getClipTopAmount();
285 }
286
287 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200288 * A listener notifying when {@link #getActualHeight} changes.
289 */
290 public interface OnHeightChangedListener {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200291
292 /**
293 * @param view the view for which the height changed, or {@code null} if just the top
294 * padding or the padding between the elements changed
295 */
Jorim Jaggibe565df2014-04-28 17:51:23 +0200296 void onHeightChanged(ExpandableView view);
Selim Cineka5e211b2014-08-11 17:35:48 +0200297
298 /**
299 * Called when the view is reset and therefore the height will change abruptly
300 *
301 * @param view The view which was reset.
302 */
303 void onReset(ExpandableView view);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200304 }
305}