blob: 9ef495de0b468fb29ed428f9959001246e178cbe [file] [log] [blame]
Chris Wren51c75102013-07-16 20:49:17 -04001/*
2 * Copyright (C) 2013 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
Selim Cinekb5605e52015-02-20 18:21:41 +010019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
Chris Wren51c75102013-07-16 20:49:17 -040022import android.content.Context;
Selim Cinekcab4a602014-09-03 14:47:57 +020023import android.graphics.drawable.AnimatedVectorDrawable;
24import android.graphics.drawable.AnimationDrawable;
Selim Cinekb5605e52015-02-20 18:21:41 +010025import android.graphics.drawable.ColorDrawable;
Selim Cinekcab4a602014-09-03 14:47:57 +020026import android.graphics.drawable.Drawable;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +020027import android.service.notification.StatusBarNotification;
Chris Wren51c75102013-07-16 20:49:17 -040028import android.util.AttributeSet;
Selim Cinek1a521f32014-11-03 17:39:29 +010029import android.view.MotionEvent;
Dan Sandlera5e0f412014-01-23 15:11:54 -050030import android.view.View;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +020031import android.view.ViewStub;
Jorim Jaggife40f7d2014-04-28 15:20:04 +020032import android.view.accessibility.AccessibilityEvent;
Selim Cinekb5605e52015-02-20 18:21:41 +010033import android.view.animation.LinearInterpolator;
Selim Cinekcab4a602014-09-03 14:47:57 +020034import android.widget.ImageView;
Selim Cinekb5605e52015-02-20 18:21:41 +010035
Dan Sandlera5e0f412014-01-23 15:11:54 -050036import com.android.systemui.R;
Selim Cinekb5605e52015-02-20 18:21:41 +010037import com.android.systemui.statusbar.phone.NotificationGroupManager;
38import com.android.systemui.statusbar.phone.PhoneStatusBar;
39import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
40import com.android.systemui.statusbar.stack.StackScrollState;
41import com.android.systemui.statusbar.stack.StackStateAnimator;
42import com.android.systemui.statusbar.stack.StackViewState;
43
44import java.util.List;
Dan Sandlera5e0f412014-01-23 15:11:54 -050045
Jorim Jaggi4222d9a2014-04-23 16:13:15 +020046public class ExpandableNotificationRow extends ActivatableNotificationView {
Selim Cinekb5605e52015-02-20 18:21:41 +010047
48 private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
49 private static final int COLORED_DIVIDER_ALPHA = 0x7B;
50 private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
Selim Cinek1685e632014-04-08 02:27:49 +020051 private int mRowMinHeight;
Chris Wren51c75102013-07-16 20:49:17 -040052
Selim Cinek1685e632014-04-08 02:27:49 +020053 /** Does this row contain layouts that can adapt to row expansion */
Chris Wren51c75102013-07-16 20:49:17 -040054 private boolean mExpandable;
Selim Cinek1685e632014-04-08 02:27:49 +020055 /** Has the user actively changed the expansion state of this row */
56 private boolean mHasUserChangedExpansion;
57 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
Chris Wren51c75102013-07-16 20:49:17 -040058 private boolean mUserExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +020059 /** Is the user touching this row */
Chris Wren51c75102013-07-16 20:49:17 -040060 private boolean mUserLocked;
Selim Cinek1685e632014-04-08 02:27:49 +020061 /** Are we showing the "public" version */
Dan Sandlera5e0f412014-01-23 15:11:54 -050062 private boolean mShowingPublic;
Jorim Jaggiae441282014-08-01 02:45:18 +020063 private boolean mSensitive;
64 private boolean mShowingPublicInitialized;
Selim Cinek60122be2015-04-15 18:16:50 -070065 private boolean mHideSensitiveForIntrinsicHeight;
Chris Wren51c75102013-07-16 20:49:17 -040066
Selim Cinek1685e632014-04-08 02:27:49 +020067 /**
68 * Is this notification expanded by the system. The expansion state can be overridden by the
69 * user expansion.
70 */
71 private boolean mIsSystemExpanded;
Jorim Jaggidce3c4c2014-04-29 23:12:24 +020072
73 /**
74 * Whether the notification expansion is disabled. This is the case on Keyguard.
75 */
76 private boolean mExpansionDisabled;
77
Jorim Jaggibe565df2014-04-28 17:51:23 +020078 private NotificationContentView mPublicLayout;
79 private NotificationContentView mPrivateLayout;
Selim Cinek1685e632014-04-08 02:27:49 +020080 private int mMaxExpandHeight;
Selim Cinek8d490d42015-04-10 00:05:50 -070081 private int mHeadsUpHeight;
Selim Cinek863834b2014-05-20 04:20:25 +020082 private View mVetoButton;
Dan Sandler0d3e62f2014-07-14 17:13:50 -040083 private boolean mClearable;
Chris Wren78403d72014-07-28 10:23:24 +010084 private ExpansionLogger mLogger;
85 private String mLoggingKey;
Selim Cineka5e211b2014-08-11 17:35:48 +020086 private boolean mWasReset;
Selim Cinek8d490d42015-04-10 00:05:50 -070087 private NotificationGuts mGuts;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +020088 private StatusBarNotification mStatusBarNotification;
Selim Cinek1a521f32014-11-03 17:39:29 +010089 private boolean mIsHeadsUp;
Selim Cinekb5605e52015-02-20 18:21:41 +010090 private View mExpandButton;
91 private View mExpandButtonDivider;
92 private ViewStub mExpandButtonStub;
93 private ViewStub mChildrenContainerStub;
94 private NotificationGroupManager mGroupManager;
95 private View mExpandButtonContainer;
96 private boolean mChildrenExpanded;
97 private NotificationChildrenContainer mChildrenContainer;
98 private ValueAnimator mChildExpandAnimator;
99 private float mChildrenExpandProgress;
100 private float mExpandButtonStart;
Selim Cinekab29aeb2015-02-20 18:18:32 +0100101 private ViewStub mGutsStub;
Selim Cinekb5605e52015-02-20 18:21:41 +0100102 private boolean mHasExpandAction;
103 private boolean mIsSystemChildExpanded;
Selim Cinek684a4422015-04-15 16:18:39 -0700104 private boolean mIsPinned;
Selim Cinekb5605e52015-02-20 18:21:41 +0100105 private OnClickListener mExpandClickListener = new OnClickListener() {
106 @Override
107 public void onClick(View v) {
108 mGroupManager.setGroupExpanded(mStatusBarNotification,
109 !mChildrenExpanded);
110 }
111 };
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200112
Selim Cinek8d490d42015-04-10 00:05:50 -0700113 public NotificationContentView getPrivateLayout() {
114 return mPrivateLayout;
115 }
116
117 public NotificationContentView getPublicLayout() {
118 return mPublicLayout;
119 }
120
Selim Cinekcab4a602014-09-03 14:47:57 +0200121 public void setIconAnimationRunning(boolean running) {
122 setIconAnimationRunning(running, mPublicLayout);
123 setIconAnimationRunning(running, mPrivateLayout);
124 }
125
126 private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
127 if (layout != null) {
128 View contractedChild = layout.getContractedChild();
129 View expandedChild = layout.getExpandedChild();
Selim Cinek8d490d42015-04-10 00:05:50 -0700130 View headsUpChild = layout.getHeadsUpChild();
Selim Cinekcab4a602014-09-03 14:47:57 +0200131 setIconAnimationRunningForChild(running, contractedChild);
132 setIconAnimationRunningForChild(running, expandedChild);
Selim Cinek8d490d42015-04-10 00:05:50 -0700133 setIconAnimationRunningForChild(running, headsUpChild);
Selim Cinekcab4a602014-09-03 14:47:57 +0200134 }
135 }
136
137 private void setIconAnimationRunningForChild(boolean running, View child) {
138 if (child != null) {
139 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
140 setIconRunning(icon, running);
141 ImageView rightIcon = (ImageView) child.findViewById(
142 com.android.internal.R.id.right_icon);
143 setIconRunning(rightIcon, running);
144 }
145 }
146
147 private void setIconRunning(ImageView imageView, boolean running) {
148 if (imageView != null) {
149 Drawable drawable = imageView.getDrawable();
150 if (drawable instanceof AnimationDrawable) {
151 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
152 if (running) {
153 animationDrawable.start();
154 } else {
155 animationDrawable.stop();
156 }
157 } else if (drawable instanceof AnimatedVectorDrawable) {
158 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
159 if (running) {
160 animationDrawable.start();
161 } else {
162 animationDrawable.stop();
163 }
164 }
165 }
166 }
167
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200168 public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
169 mStatusBarNotification = statusBarNotification;
Selim Cineka37774f2014-11-11 19:16:18 +0100170 updateVetoButton();
Selim Cinekb5605e52015-02-20 18:21:41 +0100171 updateExpandButton();
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200172 }
173
174 public StatusBarNotification getStatusBarNotification() {
175 return mStatusBarNotification;
176 }
177
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700178 public boolean isHeadsUp() {
179 return mIsHeadsUp;
180 }
181
Selim Cinek1a521f32014-11-03 17:39:29 +0100182 public void setHeadsUp(boolean isHeadsUp) {
Selim Cinekc80fdb12015-04-13 15:09:08 -0700183 int intrinsicBefore = getIntrinsicHeight();
Selim Cinek1a521f32014-11-03 17:39:29 +0100184 mIsHeadsUp = isHeadsUp;
Selim Cinek8d490d42015-04-10 00:05:50 -0700185 mPrivateLayout.setHeadsUp(isHeadsUp);
Selim Cinekc80fdb12015-04-13 15:09:08 -0700186 if (intrinsicBefore != getIntrinsicHeight()) {
187 notifyHeightChanged(false /* needsAnimation */);
188 }
Selim Cinek1a521f32014-11-03 17:39:29 +0100189 }
190
Selim Cinekb5605e52015-02-20 18:21:41 +0100191 public void setGroupManager(NotificationGroupManager groupManager) {
192 mGroupManager = groupManager;
193 }
194
195 public void addChildNotification(ExpandableNotificationRow row) {
196 addChildNotification(row, -1);
197 }
198
199 /**
200 * Add a child notification to this view.
201 *
202 * @param row the row to add
203 * @param childIndex the index to add it at, if -1 it will be added at the end
204 */
205 public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
206 if (mChildrenContainer == null) {
207 mChildrenContainerStub.inflate();
208 }
209 mChildrenContainer.addNotification(row, childIndex);
210 }
211
212 public void removeChildNotification(ExpandableNotificationRow row) {
213 if (mChildrenContainer != null) {
214 mChildrenContainer.removeNotification(row);
215 }
216 }
217
218 @Override
219 public boolean areChildrenExpanded() {
220 return mChildrenExpanded;
221 }
222
223 public List<ExpandableNotificationRow> getNotificationChildren() {
224 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
225 }
226
227 /**
228 * Apply the order given in the list to the children.
229 *
230 * @param childOrder the new list order
231 * @return whether the list order has changed
232 */
233 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
234 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
235 }
236
237 public void getChildrenStates(StackScrollState resultState) {
238 if (mChildrenExpanded) {
239 StackViewState parentState = resultState.getViewStateForView(this);
240 mChildrenContainer.getState(resultState, parentState);
241 }
242 }
243
244 public void applyChildrenState(StackScrollState state) {
245 if (mChildrenExpanded) {
246 mChildrenContainer.applyState(state);
247 }
248 }
249
250 public void prepareExpansionChanged(StackScrollState state) {
251 if (mChildrenExpanded) {
252 mChildrenContainer.prepareExpansionChanged(state);
253 }
254 }
255
256 public void startChildAnimation(StackScrollState finalState,
257 StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
258 if (mChildrenExpanded) {
259 mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
260 duration);
261 }
262 }
263
264 public ExpandableNotificationRow getViewAtPosition(float y) {
265 if (!mChildrenExpanded) {
266 return this;
267 } else {
268 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
269 return view == null ? this : view;
270 }
271 }
272
Selim Cinekab29aeb2015-02-20 18:18:32 +0100273 public NotificationGuts getGuts() {
274 return mGuts;
275 }
276
Selim Cinekb5605e52015-02-20 18:21:41 +0100277 protected int calculateContentHeightFromActualHeight(int actualHeight) {
278 int realActualHeight = actualHeight;
279 if (hasBottomDecor()) {
280 realActualHeight -= getBottomDecorHeight();
281 }
282 realActualHeight = Math.max(getMinHeight(), realActualHeight);
283 return realActualHeight;
284 }
285
Selim Cinek684a4422015-04-15 16:18:39 -0700286 /**
287 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
288 * the notification will be rendered on top of the screen.
289 *
290 * @param pinned whether it is pinned
291 */
292 public void setPinned(boolean pinned) {
293 mIsPinned = pinned;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700294 }
295
Selim Cinek684a4422015-04-15 16:18:39 -0700296 public boolean isPinned() {
297 return mIsPinned;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700298 }
299
300 public int getHeadsUpHeight() {
Selim Cinek8d490d42015-04-10 00:05:50 -0700301 return mHeadsUpHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700302 }
303
Chris Wren78403d72014-07-28 10:23:24 +0100304 public interface ExpansionLogger {
305 public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
306 }
Selim Cinek1685e632014-04-08 02:27:49 +0200307
Chris Wren51c75102013-07-16 20:49:17 -0400308 public ExpandableNotificationRow(Context context, AttributeSet attrs) {
309 super(context, attrs);
310 }
311
Christoph Studera7fe6312014-06-27 19:32:44 +0200312 /**
313 * Resets this view so it can be re-used for an updated notification.
314 */
Christoph Studer22f2ee52014-07-29 22:57:21 +0200315 @Override
Christoph Studera7fe6312014-06-27 19:32:44 +0200316 public void reset() {
Christoph Studer22f2ee52014-07-29 22:57:21 +0200317 super.reset();
Christoph Studera7fe6312014-06-27 19:32:44 +0200318 mRowMinHeight = 0;
Chris Wren78403d72014-07-28 10:23:24 +0100319 final boolean wasExpanded = isExpanded();
Selim Cinek379ff8f2015-02-20 17:03:16 +0100320 mMaxViewHeight = 0;
Christoph Studera7fe6312014-06-27 19:32:44 +0200321 mExpandable = false;
322 mHasUserChangedExpansion = false;
323 mUserLocked = false;
324 mShowingPublic = false;
Jorim Jaggiae441282014-08-01 02:45:18 +0200325 mSensitive = false;
326 mShowingPublicInitialized = false;
Christoph Studera7fe6312014-06-27 19:32:44 +0200327 mIsSystemExpanded = false;
328 mExpansionDisabled = false;
Selim Cinek1a521f32014-11-03 17:39:29 +0100329 mPublicLayout.reset(mIsHeadsUp);
330 mPrivateLayout.reset(mIsHeadsUp);
Selim Cinek31094df2014-08-14 19:28:15 +0200331 resetHeight();
332 logExpansionEvent(false, wasExpanded);
333 }
334
335 public void resetHeight() {
Selim Cinek1a521f32014-11-03 17:39:29 +0100336 if (mIsHeadsUp) {
337 resetActualHeight();
338 }
Christoph Studera7fe6312014-06-27 19:32:44 +0200339 mMaxExpandHeight = 0;
Selim Cinek8d490d42015-04-10 00:05:50 -0700340 mHeadsUpHeight = 0;
Selim Cineka5e211b2014-08-11 17:35:48 +0200341 mWasReset = true;
Selim Cinek31094df2014-08-14 19:28:15 +0200342 onHeightReset();
Selim Cinek6e28a672014-09-05 14:43:28 +0200343 requestLayout();
Christoph Studera7fe6312014-06-27 19:32:44 +0200344 }
345
Jorim Jaggi251957d2014-04-09 04:24:09 +0200346 @Override
Selim Cinek1a521f32014-11-03 17:39:29 +0100347 protected boolean filterMotionEvent(MotionEvent event) {
348 return mIsHeadsUp || super.filterMotionEvent(event);
349 }
350
351 @Override
Jorim Jaggi251957d2014-04-09 04:24:09 +0200352 protected void onFinishInflate() {
353 super.onFinishInflate();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200354 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
355 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
Selim Cinekab29aeb2015-02-20 18:18:32 +0100356 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
357 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200358 @Override
359 public void onInflate(ViewStub stub, View inflated) {
360 mGuts = (NotificationGuts) inflated;
361 mGuts.setClipTopAmount(getClipTopAmount());
362 mGuts.setActualHeight(getActualHeight());
Selim Cinekab29aeb2015-02-20 18:18:32 +0100363 mGutsStub = null;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200364 }
365 });
Selim Cinekb5605e52015-02-20 18:21:41 +0100366 mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
367 mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
368
369 @Override
370 public void onInflate(ViewStub stub, View inflated) {
371 mExpandButtonContainer = inflated;
372 mExpandButton = inflated.findViewById(R.id.notification_expand_button);
373 mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
374 mExpandButtonContainer.setOnClickListener(mExpandClickListener);
375 }
376 });
377 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
378 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
379
380 @Override
381 public void onInflate(ViewStub stub, View inflated) {
382 mChildrenContainer = (NotificationChildrenContainer) inflated;
383 mChildrenContainer.setCollapseClickListener(mExpandClickListener);
384 updateChildrenVisibility(false);
385 }
386 });
Selim Cinek863834b2014-05-20 04:20:25 +0200387 mVetoButton = findViewById(R.id.veto);
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200388 }
389
Selim Cinekab29aeb2015-02-20 18:18:32 +0100390 public void inflateGuts() {
391 if (mGuts == null) {
392 mGutsStub.inflate();
393 }
394 }
395
Selim Cinekb5605e52015-02-20 18:21:41 +0100396 private void updateChildrenVisibility(boolean animated) {
397 if (mChildrenContainer == null) {
398 return;
399 }
400 if (mChildExpandAnimator != null) {
401 mChildExpandAnimator.cancel();
402 }
403 float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
404 if (animated) {
405 if (mChildrenExpanded) {
406 mChildrenContainer.setVisibility(VISIBLE);
407 }
408 mExpandButtonStart = mExpandButtonContainer.getTranslationY();
409 mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
410 mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
411 @Override
412 public void onAnimationUpdate(ValueAnimator animation) {
413 setChildrenExpandProgress((float) animation.getAnimatedValue());
414 }
415 });
416 mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
417 @Override
418 public void onAnimationEnd(Animator animation) {
419 mChildExpandAnimator = null;
420 if (!mChildrenExpanded) {
421 mChildrenContainer.setVisibility(INVISIBLE);
422 }
423 }
424 });
425 mChildExpandAnimator.setInterpolator(mLinearInterpolator);
426 mChildExpandAnimator.setDuration(
427 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
428 mChildExpandAnimator.start();
429 } else {
430 setChildrenExpandProgress(targetProgress);
431 mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
432 }
433 }
434
435 private void setChildrenExpandProgress(float progress) {
436 mChildrenExpandProgress = progress;
437 updateExpandButtonAppearance();
438 NotificationContentView showingLayout = getShowingLayout();
439 float alpha = 1.0f - mChildrenExpandProgress;
440 alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
441 showingLayout.setAlpha(alpha);
442 }
443
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200444 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800445 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
446 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200447 // Add a record for the entire layout since its content is somehow small.
448 // The event comes from a leaf view that is interacted with.
449 AccessibilityEvent record = AccessibilityEvent.obtain();
450 onInitializeAccessibilityEvent(record);
451 dispatchPopulateAccessibilityEvent(record);
452 event.appendRecord(record);
453 return true;
454 }
455 return false;
Jorim Jaggic5dc0d02014-04-15 15:42:55 +0200456 }
Chris Wren51c75102013-07-16 20:49:17 -0400457
John Spurlocke15452b2014-08-21 09:44:39 -0400458 @Override
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100459 public void setDark(boolean dark, boolean fade, long delay) {
460 super.setDark(dark, fade, delay);
John Spurlocke15452b2014-08-21 09:44:39 -0400461 final NotificationContentView showing = getShowingLayout();
462 if (showing != null) {
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100463 showing.setDark(dark, fade, delay);
John Spurlocke15452b2014-08-21 09:44:39 -0400464 }
465 }
466
Selim Cinek1685e632014-04-08 02:27:49 +0200467 public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
468 mRowMinHeight = rowMinHeight;
Selim Cinek379ff8f2015-02-20 17:03:16 +0100469 mMaxViewHeight = rowMaxHeight;
Chris Wren51c75102013-07-16 20:49:17 -0400470 }
471
472 public boolean isExpandable() {
473 return mExpandable;
474 }
475
476 public void setExpandable(boolean expandable) {
477 mExpandable = expandable;
478 }
479
Selim Cinek1685e632014-04-08 02:27:49 +0200480 /**
481 * @return whether the user has changed the expansion state
482 */
483 public boolean hasUserChangedExpansion() {
484 return mHasUserChangedExpansion;
485 }
486
Chris Wren51c75102013-07-16 20:49:17 -0400487 public boolean isUserExpanded() {
488 return mUserExpanded;
489 }
490
Selim Cinek1685e632014-04-08 02:27:49 +0200491 /**
492 * Set this notification to be expanded by the user
493 *
494 * @param userExpanded whether the user wants this notification to be expanded
495 */
Chris Wren51c75102013-07-16 20:49:17 -0400496 public void setUserExpanded(boolean userExpanded) {
Christoph Studera7fe6312014-06-27 19:32:44 +0200497 if (userExpanded && !mExpandable) return;
Chris Wren78403d72014-07-28 10:23:24 +0100498 final boolean wasExpanded = isExpanded();
Selim Cinek1685e632014-04-08 02:27:49 +0200499 mHasUserChangedExpansion = true;
Chris Wren51c75102013-07-16 20:49:17 -0400500 mUserExpanded = userExpanded;
Chris Wren78403d72014-07-28 10:23:24 +0100501 logExpansionEvent(true, wasExpanded);
Chris Wren51c75102013-07-16 20:49:17 -0400502 }
503
Selim Cinekccd14fb2014-08-12 18:53:24 +0200504 public void resetUserExpansion() {
505 mHasUserChangedExpansion = false;
506 mUserExpanded = false;
507 }
508
Chris Wren51c75102013-07-16 20:49:17 -0400509 public boolean isUserLocked() {
510 return mUserLocked;
511 }
512
513 public void setUserLocked(boolean userLocked) {
514 mUserLocked = userLocked;
515 }
516
Selim Cinek1685e632014-04-08 02:27:49 +0200517 /**
518 * @return has the system set this notification to be expanded
519 */
520 public boolean isSystemExpanded() {
521 return mIsSystemExpanded;
522 }
523
524 /**
525 * Set this notification to be expanded by the system.
526 *
527 * @param expand whether the system wants this notification to be expanded.
528 */
529 public void setSystemExpanded(boolean expand) {
Selim Cinek31094df2014-08-14 19:28:15 +0200530 if (expand != mIsSystemExpanded) {
531 final boolean wasExpanded = isExpanded();
532 mIsSystemExpanded = expand;
Selim Cinekb5605e52015-02-20 18:21:41 +0100533 notifyHeightChanged(false /* needsAnimation */);
Selim Cinek31094df2014-08-14 19:28:15 +0200534 logExpansionEvent(false, wasExpanded);
535 }
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200536 }
537
538 /**
539 * @param expansionDisabled whether to prevent notification expansion
540 */
541 public void setExpansionDisabled(boolean expansionDisabled) {
Selim Cinek31094df2014-08-14 19:28:15 +0200542 if (expansionDisabled != mExpansionDisabled) {
543 final boolean wasExpanded = isExpanded();
544 mExpansionDisabled = expansionDisabled;
545 logExpansionEvent(false, wasExpanded);
546 if (wasExpanded != isExpanded()) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100547 notifyHeightChanged(false /* needsAnimation */);
Selim Cinek31094df2014-08-14 19:28:15 +0200548 }
549 }
Selim Cinek1685e632014-04-08 02:27:49 +0200550 }
551
552 /**
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400553 * @return Can the underlying notification be cleared?
554 */
555 public boolean isClearable() {
Selim Cineka37774f2014-11-11 19:16:18 +0100556 return mStatusBarNotification != null && mStatusBarNotification.isClearable();
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400557 }
558
559 /**
Selim Cinek1685e632014-04-08 02:27:49 +0200560 * Apply an expansion state to the layout.
Selim Cinek1685e632014-04-08 02:27:49 +0200561 */
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200562 public void applyExpansionToLayout() {
563 boolean expand = isExpanded();
Chris Wren51c75102013-07-16 20:49:17 -0400564 if (expand && mExpandable) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100565 setContentHeight(mMaxExpandHeight);
Chris Wren51c75102013-07-16 20:49:17 -0400566 } else {
Selim Cinekb5605e52015-02-20 18:21:41 +0100567 setContentHeight(mRowMinHeight);
Chris Wren51c75102013-07-16 20:49:17 -0400568 }
Chris Wren51c75102013-07-16 20:49:17 -0400569 }
Dan Sandlera5e0f412014-01-23 15:11:54 -0500570
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200571 @Override
572 public int getIntrinsicHeight() {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200573 if (isUserLocked()) {
574 return getActualHeight();
575 }
Selim Cinek1685e632014-04-08 02:27:49 +0200576 boolean inExpansionState = isExpanded();
Selim Cinekb5605e52015-02-20 18:21:41 +0100577 int maxContentHeight;
Selim Cinek60122be2015-04-15 18:16:50 -0700578 if (mSensitive && mHideSensitiveForIntrinsicHeight) {
579 return mRowMinHeight;
580 } else if (mIsHeadsUp) {
Selim Cinek8d490d42015-04-10 00:05:50 -0700581 if (inExpansionState) {
582 maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
583 } else {
584 maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
585 }
Selim Cinek60122be2015-04-15 18:16:50 -0700586 } else if ((!inExpansionState && !mChildrenExpanded)) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100587 maxContentHeight = mRowMinHeight;
588 } else if (mChildrenExpanded) {
589 maxContentHeight = mChildrenContainer.getIntrinsicHeight();
590 } else {
591 maxContentHeight = getMaxExpandHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200592 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100593 return maxContentHeight + getBottomDecorHeight();
594 }
Selim Cinek1685e632014-04-08 02:27:49 +0200595
Selim Cinekb5605e52015-02-20 18:21:41 +0100596 @Override
597 protected boolean hasBottomDecor() {
598 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
599 && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
600 }
601
602 @Override
603 protected boolean canHaveBottomDecor() {
604 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
Selim Cinek1685e632014-04-08 02:27:49 +0200605 }
606
Selim Cinek1685e632014-04-08 02:27:49 +0200607 /**
608 * Check whether the view state is currently expanded. This is given by the system in {@link
609 * #setSystemExpanded(boolean)} and can be overridden by user expansion or
610 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
611 * view can differ from this state, if layout params are modified from outside.
612 *
613 * @return whether the view state is currently expanded.
614 */
615 private boolean isExpanded() {
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200616 return !mExpansionDisabled
Selim Cinekb5605e52015-02-20 18:21:41 +0100617 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
618 || isUserExpanded());
619 }
620
621 private boolean isSystemChildExpanded() {
622 return mIsSystemChildExpanded;
623 }
624
625 public void setSystemChildExpanded(boolean expanded) {
626 mIsSystemChildExpanded = expanded;
Selim Cinek1685e632014-04-08 02:27:49 +0200627 }
628
629 @Override
630 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
631 super.onLayout(changed, left, top, right, bottom);
Selim Cineka5e211b2014-08-11 17:35:48 +0200632 boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
Selim Cinek8d490d42015-04-10 00:05:50 -0700633 updateMaxHeights();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200634 if (updateExpandHeight) {
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200635 applyExpansionToLayout();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200636 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200637 mWasReset = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200638 }
639
Selim Cinekb5605e52015-02-20 18:21:41 +0100640 @Override
641 protected boolean isChildInvisible(View child) {
642
643 // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
644 // view will get too high and the shadows will be off.
645 boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
646 return super.isChildInvisible(child) || isInvisibleChildContainer;
647 }
648
Selim Cinek8d490d42015-04-10 00:05:50 -0700649 private void updateMaxHeights() {
Selim Cinekd2319fb2014-09-01 19:41:54 +0200650 int intrinsicBefore = getIntrinsicHeight();
Selim Cinek8d490d42015-04-10 00:05:50 -0700651 View expandedChild = mPrivateLayout.getExpandedChild();
652 if (expandedChild == null) {
653 expandedChild = mPrivateLayout.getContractedChild();
654 }
655 mMaxExpandHeight = expandedChild.getHeight();
656 View headsUpChild = mPrivateLayout.getHeadsUpChild();
Selim Cinek1f3f5442015-04-10 17:54:46 -0700657 if (headsUpChild == null) {
658 headsUpChild = mPrivateLayout.getContractedChild();
Selim Cinek8d490d42015-04-10 00:05:50 -0700659 }
Selim Cinek1f3f5442015-04-10 17:54:46 -0700660 mHeadsUpHeight = headsUpChild.getHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +0200661 if (intrinsicBefore != getIntrinsicHeight()) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100662 notifyHeightChanged(false /* needsAnimation */);
Selim Cinekd2319fb2014-09-01 19:41:54 +0200663 }
664 }
665
Jorim Jaggiae441282014-08-01 02:45:18 +0200666 public void setSensitive(boolean sensitive) {
667 mSensitive = sensitive;
668 }
669
670 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
Selim Cinek60122be2015-04-15 18:16:50 -0700671 mHideSensitiveForIntrinsicHeight = hideSensitive;
Jorim Jaggiae441282014-08-01 02:45:18 +0200672 }
673
674 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
675 long duration) {
676 boolean oldShowingPublic = mShowingPublic;
677 mShowingPublic = mSensitive && hideSensitive;
678 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
679 return;
680 }
Dan Sandlera5e0f412014-01-23 15:11:54 -0500681
682 // bail out if no public version
Selim Cinek1685e632014-04-08 02:27:49 +0200683 if (mPublicLayout.getChildCount() == 0) return;
Dan Sandlera5e0f412014-01-23 15:11:54 -0500684
Jorim Jaggiae441282014-08-01 02:45:18 +0200685 if (!animated) {
686 mPublicLayout.animate().cancel();
687 mPrivateLayout.animate().cancel();
688 mPublicLayout.setAlpha(1f);
689 mPrivateLayout.setAlpha(1f);
690 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
691 mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
692 } else {
693 animateShowingPublic(delay, duration);
694 }
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400695
696 updateVetoButton();
Jorim Jaggiae441282014-08-01 02:45:18 +0200697 mShowingPublicInitialized = true;
698 }
699
700 private void animateShowingPublic(long delay, long duration) {
701 final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
702 View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
703 source.setVisibility(View.VISIBLE);
704 target.setVisibility(View.VISIBLE);
705 target.setAlpha(0f);
706 source.animate().cancel();
707 target.animate().cancel();
708 source.animate()
709 .alpha(0f)
Jorim Jaggiae441282014-08-01 02:45:18 +0200710 .setStartDelay(delay)
711 .setDuration(duration)
712 .withEndAction(new Runnable() {
713 @Override
714 public void run() {
715 source.setVisibility(View.INVISIBLE);
716 }
717 });
718 target.animate()
719 .alpha(1f)
Jorim Jaggiae441282014-08-01 02:45:18 +0200720 .setStartDelay(delay)
721 .setDuration(duration);
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400722 }
723
724 private void updateVetoButton() {
725 // public versions cannot be dismissed
726 mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
Dan Sandlera5e0f412014-01-23 15:11:54 -0500727 }
Jorim Jaggi251957d2014-04-09 04:24:09 +0200728
Selim Cinekb5605e52015-02-20 18:21:41 +0100729 public void setChildrenExpanded(boolean expanded, boolean animate) {
730 mChildrenExpanded = expanded;
731 updateChildrenVisibility(animate);
732 }
733
734 public void updateExpandButton() {
735 boolean hasExpand = hasBottomDecor();
736 if (hasExpand != mHasExpandAction) {
737 if (hasExpand) {
738 if (mExpandButtonContainer == null) {
739 mExpandButtonStub.inflate();
740 }
741 mExpandButtonContainer.setVisibility(View.VISIBLE);
742 updateExpandButtonAppearance();
743 updateExpandButtonColor();
744 } else if (mExpandButtonContainer != null) {
745 mExpandButtonContainer.setVisibility(View.GONE);
746 }
747 notifyHeightChanged(true /* needsAnimation */);
748 }
749 mHasExpandAction = hasExpand;
750 }
751
752 private void updateExpandButtonAppearance() {
753 if (mExpandButtonContainer == null) {
754 return;
755 }
756 float expandButtonAlpha = 0.0f;
757 float expandButtonTranslation = 0.0f;
758 float containerTranslation = 0.0f;
759 int minHeight = getMinHeight();
760 if (!mChildrenExpanded || mChildExpandAnimator != null) {
761 int expandActionHeight = getBottomDecorHeight();
762 int translationY = getActualHeight() - expandActionHeight;
763 if (translationY > minHeight) {
764 containerTranslation = translationY;
765 expandButtonAlpha = 1.0f;
766 expandButtonTranslation = 0.0f;
767 } else {
768 containerTranslation = minHeight;
769 float progress = expandActionHeight != 0
770 ? (minHeight - translationY) / (float) expandActionHeight
771 : 1.0f;
772 expandButtonTranslation = -progress * expandActionHeight * 0.7f;
773 float alphaProgress = Math.min(progress / 0.7f, 1.0f);
774 alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
775 expandButtonAlpha = 1.0f - alphaProgress;
776 }
777 }
778 if (mChildExpandAnimator != null || mChildrenExpanded) {
779 expandButtonAlpha = (1.0f - mChildrenExpandProgress)
780 * expandButtonAlpha;
781 expandButtonTranslation = (1.0f - mChildrenExpandProgress)
782 * expandButtonTranslation;
783 float newTranslation = -getBottomDecorHeight();
784
785 // We don't want to take the actual height of the view as this is already
786 // interpolated by a custom interpolator leading to a confusing animation. We want
787 // to have a stable end value to interpolate in between
788 float collapsedHeight = !mChildrenExpanded
789 ? Math.max(StackStateAnimator.getFinalActualHeight(this)
790 - getBottomDecorHeight(), minHeight)
791 : mExpandButtonStart;
792 float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
793 mChildrenExpandProgress);
794 containerTranslation = (1.0f - translationProgress) * collapsedHeight
795 + translationProgress * newTranslation;
796 }
797 mExpandButton.setAlpha(expandButtonAlpha);
798 mExpandButtonDivider.setAlpha(expandButtonAlpha);
799 mExpandButton.setTranslationY(expandButtonTranslation);
800 mExpandButtonContainer.setTranslationY(containerTranslation);
801 NotificationContentView showingLayout = getShowingLayout();
802 float layoutTranslation =
803 mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
804 layoutTranslation = Math.min(layoutTranslation, 0);
805 if (!mChildrenExpanded && mChildExpandAnimator == null) {
806 // Needed for the DragDownHelper in order not to jump there, as the position
807 // can be negative for a short time.
808 layoutTranslation = 0;
809 }
810 showingLayout.setTranslationY(layoutTranslation);
811 if (mChildrenContainer != null) {
812 mChildrenContainer.setTranslationY(
813 mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
814 }
815 }
816
817 private void updateExpandButtonColor() {
818 // TODO: This needs some more baking, currently only the divider is colored according to
819 // the tint, but legacy black doesn't work yet perfectly for the button etc.
820 int color = getRippleColor();
821 if (color == mNormalRippleColor) {
822 color = 0;
823 }
824 if (mExpandButtonDivider != null) {
825 applyTint(mExpandButtonDivider, color);
826 }
827 if (mChildrenContainer != null) {
828 mChildrenContainer.setTintColor(color);
829 }
830 }
831
832 public static void applyTint(View v, int color) {
833 int alpha;
834 if (color != 0) {
835 alpha = COLORED_DIVIDER_ALPHA;
836 } else {
837 color = 0xff000000;
838 alpha = DEFAULT_DIVIDER_ALPHA;
839 }
840 if (v.getBackground() instanceof ColorDrawable) {
841 ColorDrawable background = (ColorDrawable) v.getBackground();
842 background.mutate();
843 background.setColor(color);
844 background.setAlpha(alpha);
845 }
846 }
847
Selim Cinek1685e632014-04-08 02:27:49 +0200848 public int getMaxExpandHeight() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100849 return mMaxExpandHeight;
Chris Wren51c75102013-07-16 20:49:17 -0400850 }
Jorim Jaggi584a7aa2014-04-10 23:26:13 +0200851
Jorim Jaggibe565df2014-04-28 17:51:23 +0200852 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200853 public boolean isContentExpandable() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200854 NotificationContentView showingLayout = getShowingLayout();
855 return showingLayout.isContentExpandable();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200856 }
857
858 @Override
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200859 public void setActualHeight(int height, boolean notifyListeners) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100860 super.setActualHeight(height, notifyListeners);
861 int contentHeight = calculateContentHeightFromActualHeight(height);
862 mPrivateLayout.setContentHeight(contentHeight);
863 mPublicLayout.setContentHeight(contentHeight);
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200864 if (mGuts != null) {
865 mGuts.setActualHeight(height);
866 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200867 invalidate();
Selim Cinekb5605e52015-02-20 18:21:41 +0100868 updateExpandButtonAppearance();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200869 }
870
871 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +0100872 public int getMaxContentHeight() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200873 NotificationContentView showingLayout = getShowingLayout();
874 return showingLayout.getMaxHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200875 }
876
877 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200878 public int getMinHeight() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200879 NotificationContentView showingLayout = getShowingLayout();
880 return showingLayout.getMinHeight();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200881 }
882
883 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200884 public void setClipTopAmount(int clipTopAmount) {
885 super.setClipTopAmount(clipTopAmount);
886 mPrivateLayout.setClipTopAmount(clipTopAmount);
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200887 mPublicLayout.setClipTopAmount(clipTopAmount);
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200888 if (mGuts != null) {
889 mGuts.setClipTopAmount(clipTopAmount);
890 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200891 }
892
893 public void notifyContentUpdated() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200894 mPublicLayout.notifyContentUpdated();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200895 mPrivateLayout.notifyContentUpdated();
Selim Cinek343e6e22014-04-11 21:23:30 +0200896 }
Selim Cinek7d447722014-06-10 15:51:59 +0200897
Selim Cinek31094df2014-08-14 19:28:15 +0200898 public boolean isMaxExpandHeightInitialized() {
899 return mMaxExpandHeight != 0;
Selim Cinek7d447722014-06-10 15:51:59 +0200900 }
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200901
902 private NotificationContentView getShowingLayout() {
903 return mShowingPublic ? mPublicLayout : mPrivateLayout;
904 }
Chris Wren78403d72014-07-28 10:23:24 +0100905
906 public void setExpansionLogger(ExpansionLogger logger, String key) {
907 mLogger = logger;
908 mLoggingKey = key;
909 }
910
911
912 private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
913 final boolean nowExpanded = isExpanded();
914 if (wasExpanded != nowExpanded && mLogger != null) {
915 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
916 }
917 }
Chris Wren51c75102013-07-16 20:49:17 -0400918}