blob: cb8217e26f2f75acc309683ebd24745c0439e761 [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;
Chris Wren78403d72014-07-28 10:23:24 +010087
Selim Cinek8d490d42015-04-10 00:05:50 -070088 private NotificationGuts mGuts;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +020089 private StatusBarNotification mStatusBarNotification;
Selim Cinek1a521f32014-11-03 17:39:29 +010090 private boolean mIsHeadsUp;
Selim Cinekb5605e52015-02-20 18:21:41 +010091 private View mExpandButton;
92 private View mExpandButtonDivider;
93 private ViewStub mExpandButtonStub;
94 private ViewStub mChildrenContainerStub;
95 private NotificationGroupManager mGroupManager;
96 private View mExpandButtonContainer;
97 private boolean mChildrenExpanded;
98 private NotificationChildrenContainer mChildrenContainer;
99 private ValueAnimator mChildExpandAnimator;
100 private float mChildrenExpandProgress;
101 private float mExpandButtonStart;
Selim Cinekab29aeb2015-02-20 18:18:32 +0100102 private ViewStub mGutsStub;
Selim Cinekb5605e52015-02-20 18:21:41 +0100103 private boolean mHasExpandAction;
104 private boolean mIsSystemChildExpanded;
105 private OnClickListener mExpandClickListener = new OnClickListener() {
106 @Override
107 public void onClick(View v) {
108 mGroupManager.setGroupExpanded(mStatusBarNotification,
109 !mChildrenExpanded);
110 }
111 };
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700112 private boolean mInShade;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200113
Selim Cinek8d490d42015-04-10 00:05:50 -0700114 public NotificationContentView getPrivateLayout() {
115 return mPrivateLayout;
116 }
117
118 public NotificationContentView getPublicLayout() {
119 return mPublicLayout;
120 }
121
Selim Cinekcab4a602014-09-03 14:47:57 +0200122 public void setIconAnimationRunning(boolean running) {
123 setIconAnimationRunning(running, mPublicLayout);
124 setIconAnimationRunning(running, mPrivateLayout);
125 }
126
127 private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
128 if (layout != null) {
129 View contractedChild = layout.getContractedChild();
130 View expandedChild = layout.getExpandedChild();
Selim Cinek8d490d42015-04-10 00:05:50 -0700131 View headsUpChild = layout.getHeadsUpChild();
Selim Cinekcab4a602014-09-03 14:47:57 +0200132 setIconAnimationRunningForChild(running, contractedChild);
133 setIconAnimationRunningForChild(running, expandedChild);
Selim Cinek8d490d42015-04-10 00:05:50 -0700134 setIconAnimationRunningForChild(running, headsUpChild);
Selim Cinekcab4a602014-09-03 14:47:57 +0200135 }
136 }
137
138 private void setIconAnimationRunningForChild(boolean running, View child) {
139 if (child != null) {
140 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
141 setIconRunning(icon, running);
142 ImageView rightIcon = (ImageView) child.findViewById(
143 com.android.internal.R.id.right_icon);
144 setIconRunning(rightIcon, running);
145 }
146 }
147
148 private void setIconRunning(ImageView imageView, boolean running) {
149 if (imageView != null) {
150 Drawable drawable = imageView.getDrawable();
151 if (drawable instanceof AnimationDrawable) {
152 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
153 if (running) {
154 animationDrawable.start();
155 } else {
156 animationDrawable.stop();
157 }
158 } else if (drawable instanceof AnimatedVectorDrawable) {
159 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
160 if (running) {
161 animationDrawable.start();
162 } else {
163 animationDrawable.stop();
164 }
165 }
166 }
167 }
168
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200169 public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
170 mStatusBarNotification = statusBarNotification;
Selim Cineka37774f2014-11-11 19:16:18 +0100171 updateVetoButton();
Selim Cinekb5605e52015-02-20 18:21:41 +0100172 updateExpandButton();
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200173 }
174
175 public StatusBarNotification getStatusBarNotification() {
176 return mStatusBarNotification;
177 }
178
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700179 public boolean isHeadsUp() {
180 return mIsHeadsUp;
181 }
182
Selim Cinek1a521f32014-11-03 17:39:29 +0100183 public void setHeadsUp(boolean isHeadsUp) {
Selim Cinekc80fdb12015-04-13 15:09:08 -0700184 int intrinsicBefore = getIntrinsicHeight();
Selim Cinek1a521f32014-11-03 17:39:29 +0100185 mIsHeadsUp = isHeadsUp;
Selim Cinek8d490d42015-04-10 00:05:50 -0700186 mPrivateLayout.setHeadsUp(isHeadsUp);
Selim Cinekc80fdb12015-04-13 15:09:08 -0700187 if (intrinsicBefore != getIntrinsicHeight()) {
188 notifyHeightChanged(false /* needsAnimation */);
189 }
Selim Cinek1a521f32014-11-03 17:39:29 +0100190 }
191
Selim Cinekb5605e52015-02-20 18:21:41 +0100192 public void setGroupManager(NotificationGroupManager groupManager) {
193 mGroupManager = groupManager;
194 }
195
196 public void addChildNotification(ExpandableNotificationRow row) {
197 addChildNotification(row, -1);
198 }
199
200 /**
201 * Add a child notification to this view.
202 *
203 * @param row the row to add
204 * @param childIndex the index to add it at, if -1 it will be added at the end
205 */
206 public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
207 if (mChildrenContainer == null) {
208 mChildrenContainerStub.inflate();
209 }
210 mChildrenContainer.addNotification(row, childIndex);
211 }
212
213 public void removeChildNotification(ExpandableNotificationRow row) {
214 if (mChildrenContainer != null) {
215 mChildrenContainer.removeNotification(row);
216 }
217 }
218
219 @Override
220 public boolean areChildrenExpanded() {
221 return mChildrenExpanded;
222 }
223
224 public List<ExpandableNotificationRow> getNotificationChildren() {
225 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
226 }
227
228 /**
229 * Apply the order given in the list to the children.
230 *
231 * @param childOrder the new list order
232 * @return whether the list order has changed
233 */
234 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
235 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
236 }
237
238 public void getChildrenStates(StackScrollState resultState) {
239 if (mChildrenExpanded) {
240 StackViewState parentState = resultState.getViewStateForView(this);
241 mChildrenContainer.getState(resultState, parentState);
242 }
243 }
244
245 public void applyChildrenState(StackScrollState state) {
246 if (mChildrenExpanded) {
247 mChildrenContainer.applyState(state);
248 }
249 }
250
251 public void prepareExpansionChanged(StackScrollState state) {
252 if (mChildrenExpanded) {
253 mChildrenContainer.prepareExpansionChanged(state);
254 }
255 }
256
257 public void startChildAnimation(StackScrollState finalState,
258 StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
259 if (mChildrenExpanded) {
260 mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
261 duration);
262 }
263 }
264
265 public ExpandableNotificationRow getViewAtPosition(float y) {
266 if (!mChildrenExpanded) {
267 return this;
268 } else {
269 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
270 return view == null ? this : view;
271 }
272 }
273
Selim Cinekab29aeb2015-02-20 18:18:32 +0100274 public NotificationGuts getGuts() {
275 return mGuts;
276 }
277
Selim Cinekb5605e52015-02-20 18:21:41 +0100278 protected int calculateContentHeightFromActualHeight(int actualHeight) {
279 int realActualHeight = actualHeight;
280 if (hasBottomDecor()) {
281 realActualHeight -= getBottomDecorHeight();
282 }
283 realActualHeight = Math.max(getMinHeight(), realActualHeight);
284 return realActualHeight;
285 }
286
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700287 public void setInShade(boolean inShade) {
288 mInShade = inShade;
289 }
290
291 public boolean isInShade() {
292 return mInShade;
293 }
294
295 public int getHeadsUpHeight() {
Selim Cinek8d490d42015-04-10 00:05:50 -0700296 return mHeadsUpHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700297 }
298
Chris Wren78403d72014-07-28 10:23:24 +0100299 public interface ExpansionLogger {
300 public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
301 }
Selim Cinek1685e632014-04-08 02:27:49 +0200302
Chris Wren51c75102013-07-16 20:49:17 -0400303 public ExpandableNotificationRow(Context context, AttributeSet attrs) {
304 super(context, attrs);
305 }
306
Christoph Studera7fe6312014-06-27 19:32:44 +0200307 /**
308 * Resets this view so it can be re-used for an updated notification.
309 */
Christoph Studer22f2ee52014-07-29 22:57:21 +0200310 @Override
Christoph Studera7fe6312014-06-27 19:32:44 +0200311 public void reset() {
Christoph Studer22f2ee52014-07-29 22:57:21 +0200312 super.reset();
Christoph Studera7fe6312014-06-27 19:32:44 +0200313 mRowMinHeight = 0;
Chris Wren78403d72014-07-28 10:23:24 +0100314 final boolean wasExpanded = isExpanded();
Selim Cinek379ff8f2015-02-20 17:03:16 +0100315 mMaxViewHeight = 0;
Christoph Studera7fe6312014-06-27 19:32:44 +0200316 mExpandable = false;
317 mHasUserChangedExpansion = false;
318 mUserLocked = false;
319 mShowingPublic = false;
Jorim Jaggiae441282014-08-01 02:45:18 +0200320 mSensitive = false;
321 mShowingPublicInitialized = false;
Christoph Studera7fe6312014-06-27 19:32:44 +0200322 mIsSystemExpanded = false;
323 mExpansionDisabled = false;
Selim Cinek1a521f32014-11-03 17:39:29 +0100324 mPublicLayout.reset(mIsHeadsUp);
325 mPrivateLayout.reset(mIsHeadsUp);
Selim Cinek31094df2014-08-14 19:28:15 +0200326 resetHeight();
327 logExpansionEvent(false, wasExpanded);
328 }
329
330 public void resetHeight() {
Selim Cinek1a521f32014-11-03 17:39:29 +0100331 if (mIsHeadsUp) {
332 resetActualHeight();
333 }
Christoph Studera7fe6312014-06-27 19:32:44 +0200334 mMaxExpandHeight = 0;
Selim Cinek8d490d42015-04-10 00:05:50 -0700335 mHeadsUpHeight = 0;
Selim Cineka5e211b2014-08-11 17:35:48 +0200336 mWasReset = true;
Selim Cinek31094df2014-08-14 19:28:15 +0200337 onHeightReset();
Selim Cinek6e28a672014-09-05 14:43:28 +0200338 requestLayout();
Christoph Studera7fe6312014-06-27 19:32:44 +0200339 }
340
Jorim Jaggi251957d2014-04-09 04:24:09 +0200341 @Override
Selim Cinek1a521f32014-11-03 17:39:29 +0100342 protected boolean filterMotionEvent(MotionEvent event) {
343 return mIsHeadsUp || super.filterMotionEvent(event);
344 }
345
346 @Override
Jorim Jaggi251957d2014-04-09 04:24:09 +0200347 protected void onFinishInflate() {
348 super.onFinishInflate();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200349 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
350 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
Selim Cinekab29aeb2015-02-20 18:18:32 +0100351 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
352 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200353 @Override
354 public void onInflate(ViewStub stub, View inflated) {
355 mGuts = (NotificationGuts) inflated;
356 mGuts.setClipTopAmount(getClipTopAmount());
357 mGuts.setActualHeight(getActualHeight());
Selim Cinekab29aeb2015-02-20 18:18:32 +0100358 mGutsStub = null;
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200359 }
360 });
Selim Cinekb5605e52015-02-20 18:21:41 +0100361 mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
362 mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
363
364 @Override
365 public void onInflate(ViewStub stub, View inflated) {
366 mExpandButtonContainer = inflated;
367 mExpandButton = inflated.findViewById(R.id.notification_expand_button);
368 mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
369 mExpandButtonContainer.setOnClickListener(mExpandClickListener);
370 }
371 });
372 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
373 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
374
375 @Override
376 public void onInflate(ViewStub stub, View inflated) {
377 mChildrenContainer = (NotificationChildrenContainer) inflated;
378 mChildrenContainer.setCollapseClickListener(mExpandClickListener);
379 updateChildrenVisibility(false);
380 }
381 });
Selim Cinek863834b2014-05-20 04:20:25 +0200382 mVetoButton = findViewById(R.id.veto);
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200383 }
384
Selim Cinekab29aeb2015-02-20 18:18:32 +0100385 public void inflateGuts() {
386 if (mGuts == null) {
387 mGutsStub.inflate();
388 }
389 }
390
Selim Cinekb5605e52015-02-20 18:21:41 +0100391 private void updateChildrenVisibility(boolean animated) {
392 if (mChildrenContainer == null) {
393 return;
394 }
395 if (mChildExpandAnimator != null) {
396 mChildExpandAnimator.cancel();
397 }
398 float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
399 if (animated) {
400 if (mChildrenExpanded) {
401 mChildrenContainer.setVisibility(VISIBLE);
402 }
403 mExpandButtonStart = mExpandButtonContainer.getTranslationY();
404 mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
405 mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
406 @Override
407 public void onAnimationUpdate(ValueAnimator animation) {
408 setChildrenExpandProgress((float) animation.getAnimatedValue());
409 }
410 });
411 mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
412 @Override
413 public void onAnimationEnd(Animator animation) {
414 mChildExpandAnimator = null;
415 if (!mChildrenExpanded) {
416 mChildrenContainer.setVisibility(INVISIBLE);
417 }
418 }
419 });
420 mChildExpandAnimator.setInterpolator(mLinearInterpolator);
421 mChildExpandAnimator.setDuration(
422 StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
423 mChildExpandAnimator.start();
424 } else {
425 setChildrenExpandProgress(targetProgress);
426 mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
427 }
428 }
429
430 private void setChildrenExpandProgress(float progress) {
431 mChildrenExpandProgress = progress;
432 updateExpandButtonAppearance();
433 NotificationContentView showingLayout = getShowingLayout();
434 float alpha = 1.0f - mChildrenExpandProgress;
435 alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
436 showingLayout.setAlpha(alpha);
437 }
438
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200439 @Override
Alan Viverettea54956a2015-01-07 16:05:02 -0800440 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
441 if (super.onRequestSendAccessibilityEventInternal(child, event)) {
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200442 // Add a record for the entire layout since its content is somehow small.
443 // The event comes from a leaf view that is interacted with.
444 AccessibilityEvent record = AccessibilityEvent.obtain();
445 onInitializeAccessibilityEvent(record);
446 dispatchPopulateAccessibilityEvent(record);
447 event.appendRecord(record);
448 return true;
449 }
450 return false;
Jorim Jaggic5dc0d02014-04-15 15:42:55 +0200451 }
Chris Wren51c75102013-07-16 20:49:17 -0400452
John Spurlocke15452b2014-08-21 09:44:39 -0400453 @Override
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100454 public void setDark(boolean dark, boolean fade, long delay) {
455 super.setDark(dark, fade, delay);
John Spurlocke15452b2014-08-21 09:44:39 -0400456 final NotificationContentView showing = getShowingLayout();
457 if (showing != null) {
Jorim Jaggi4e857f42014-11-17 19:14:04 +0100458 showing.setDark(dark, fade, delay);
John Spurlocke15452b2014-08-21 09:44:39 -0400459 }
460 }
461
Selim Cinek1685e632014-04-08 02:27:49 +0200462 public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
463 mRowMinHeight = rowMinHeight;
Selim Cinek379ff8f2015-02-20 17:03:16 +0100464 mMaxViewHeight = rowMaxHeight;
Chris Wren51c75102013-07-16 20:49:17 -0400465 }
466
467 public boolean isExpandable() {
468 return mExpandable;
469 }
470
471 public void setExpandable(boolean expandable) {
472 mExpandable = expandable;
473 }
474
Selim Cinek1685e632014-04-08 02:27:49 +0200475 /**
476 * @return whether the user has changed the expansion state
477 */
478 public boolean hasUserChangedExpansion() {
479 return mHasUserChangedExpansion;
480 }
481
Chris Wren51c75102013-07-16 20:49:17 -0400482 public boolean isUserExpanded() {
483 return mUserExpanded;
484 }
485
Selim Cinek1685e632014-04-08 02:27:49 +0200486 /**
487 * Set this notification to be expanded by the user
488 *
489 * @param userExpanded whether the user wants this notification to be expanded
490 */
Chris Wren51c75102013-07-16 20:49:17 -0400491 public void setUserExpanded(boolean userExpanded) {
Christoph Studera7fe6312014-06-27 19:32:44 +0200492 if (userExpanded && !mExpandable) return;
Chris Wren78403d72014-07-28 10:23:24 +0100493 final boolean wasExpanded = isExpanded();
Selim Cinek1685e632014-04-08 02:27:49 +0200494 mHasUserChangedExpansion = true;
Chris Wren51c75102013-07-16 20:49:17 -0400495 mUserExpanded = userExpanded;
Chris Wren78403d72014-07-28 10:23:24 +0100496 logExpansionEvent(true, wasExpanded);
Chris Wren51c75102013-07-16 20:49:17 -0400497 }
498
Selim Cinekccd14fb2014-08-12 18:53:24 +0200499 public void resetUserExpansion() {
500 mHasUserChangedExpansion = false;
501 mUserExpanded = false;
502 }
503
Chris Wren51c75102013-07-16 20:49:17 -0400504 public boolean isUserLocked() {
505 return mUserLocked;
506 }
507
508 public void setUserLocked(boolean userLocked) {
509 mUserLocked = userLocked;
510 }
511
Selim Cinek1685e632014-04-08 02:27:49 +0200512 /**
513 * @return has the system set this notification to be expanded
514 */
515 public boolean isSystemExpanded() {
516 return mIsSystemExpanded;
517 }
518
519 /**
520 * Set this notification to be expanded by the system.
521 *
522 * @param expand whether the system wants this notification to be expanded.
523 */
524 public void setSystemExpanded(boolean expand) {
Selim Cinek31094df2014-08-14 19:28:15 +0200525 if (expand != mIsSystemExpanded) {
526 final boolean wasExpanded = isExpanded();
527 mIsSystemExpanded = expand;
Selim Cinekb5605e52015-02-20 18:21:41 +0100528 notifyHeightChanged(false /* needsAnimation */);
Selim Cinek31094df2014-08-14 19:28:15 +0200529 logExpansionEvent(false, wasExpanded);
530 }
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200531 }
532
533 /**
534 * @param expansionDisabled whether to prevent notification expansion
535 */
536 public void setExpansionDisabled(boolean expansionDisabled) {
Selim Cinek31094df2014-08-14 19:28:15 +0200537 if (expansionDisabled != mExpansionDisabled) {
538 final boolean wasExpanded = isExpanded();
539 mExpansionDisabled = expansionDisabled;
540 logExpansionEvent(false, wasExpanded);
541 if (wasExpanded != isExpanded()) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100542 notifyHeightChanged(false /* needsAnimation */);
Selim Cinek31094df2014-08-14 19:28:15 +0200543 }
544 }
Selim Cinek1685e632014-04-08 02:27:49 +0200545 }
546
547 /**
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400548 * @return Can the underlying notification be cleared?
549 */
550 public boolean isClearable() {
Selim Cineka37774f2014-11-11 19:16:18 +0100551 return mStatusBarNotification != null && mStatusBarNotification.isClearable();
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400552 }
553
554 /**
Selim Cinek1685e632014-04-08 02:27:49 +0200555 * Apply an expansion state to the layout.
Selim Cinek1685e632014-04-08 02:27:49 +0200556 */
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200557 public void applyExpansionToLayout() {
558 boolean expand = isExpanded();
Chris Wren51c75102013-07-16 20:49:17 -0400559 if (expand && mExpandable) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100560 setContentHeight(mMaxExpandHeight);
Chris Wren51c75102013-07-16 20:49:17 -0400561 } else {
Selim Cinekb5605e52015-02-20 18:21:41 +0100562 setContentHeight(mRowMinHeight);
Chris Wren51c75102013-07-16 20:49:17 -0400563 }
Chris Wren51c75102013-07-16 20:49:17 -0400564 }
Dan Sandlera5e0f412014-01-23 15:11:54 -0500565
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200566 @Override
567 public int getIntrinsicHeight() {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200568 if (isUserLocked()) {
569 return getActualHeight();
570 }
Selim Cinek1685e632014-04-08 02:27:49 +0200571 boolean inExpansionState = isExpanded();
Selim Cinekb5605e52015-02-20 18:21:41 +0100572 int maxContentHeight;
Selim Cinek60122be2015-04-15 18:16:50 -0700573 if (mSensitive && mHideSensitiveForIntrinsicHeight) {
574 return mRowMinHeight;
575 } else if (mIsHeadsUp) {
Selim Cinek8d490d42015-04-10 00:05:50 -0700576 if (inExpansionState) {
577 maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
578 } else {
579 maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
580 }
Selim Cinek60122be2015-04-15 18:16:50 -0700581 } else if ((!inExpansionState && !mChildrenExpanded)) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100582 maxContentHeight = mRowMinHeight;
583 } else if (mChildrenExpanded) {
584 maxContentHeight = mChildrenContainer.getIntrinsicHeight();
585 } else {
586 maxContentHeight = getMaxExpandHeight();
Selim Cinek1685e632014-04-08 02:27:49 +0200587 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100588 return maxContentHeight + getBottomDecorHeight();
589 }
Selim Cinek1685e632014-04-08 02:27:49 +0200590
Selim Cinekb5605e52015-02-20 18:21:41 +0100591 @Override
592 protected boolean hasBottomDecor() {
593 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
594 && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
595 }
596
597 @Override
598 protected boolean canHaveBottomDecor() {
599 return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
Selim Cinek1685e632014-04-08 02:27:49 +0200600 }
601
Selim Cinek1685e632014-04-08 02:27:49 +0200602 /**
603 * Check whether the view state is currently expanded. This is given by the system in {@link
604 * #setSystemExpanded(boolean)} and can be overridden by user expansion or
605 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
606 * view can differ from this state, if layout params are modified from outside.
607 *
608 * @return whether the view state is currently expanded.
609 */
610 private boolean isExpanded() {
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200611 return !mExpansionDisabled
Selim Cinekb5605e52015-02-20 18:21:41 +0100612 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
613 || isUserExpanded());
614 }
615
616 private boolean isSystemChildExpanded() {
617 return mIsSystemChildExpanded;
618 }
619
620 public void setSystemChildExpanded(boolean expanded) {
621 mIsSystemChildExpanded = expanded;
Selim Cinek1685e632014-04-08 02:27:49 +0200622 }
623
624 @Override
625 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
626 super.onLayout(changed, left, top, right, bottom);
Selim Cineka5e211b2014-08-11 17:35:48 +0200627 boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
Selim Cinek8d490d42015-04-10 00:05:50 -0700628 updateMaxHeights();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200629 if (updateExpandHeight) {
Jorim Jaggidce3c4c2014-04-29 23:12:24 +0200630 applyExpansionToLayout();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200631 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200632 mWasReset = false;
Selim Cinek1685e632014-04-08 02:27:49 +0200633 }
634
Selim Cinekb5605e52015-02-20 18:21:41 +0100635 @Override
636 protected boolean isChildInvisible(View child) {
637
638 // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
639 // view will get too high and the shadows will be off.
640 boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
641 return super.isChildInvisible(child) || isInvisibleChildContainer;
642 }
643
Selim Cinek8d490d42015-04-10 00:05:50 -0700644 private void updateMaxHeights() {
Selim Cinekd2319fb2014-09-01 19:41:54 +0200645 int intrinsicBefore = getIntrinsicHeight();
Selim Cinek8d490d42015-04-10 00:05:50 -0700646 View expandedChild = mPrivateLayout.getExpandedChild();
647 if (expandedChild == null) {
648 expandedChild = mPrivateLayout.getContractedChild();
649 }
650 mMaxExpandHeight = expandedChild.getHeight();
651 View headsUpChild = mPrivateLayout.getHeadsUpChild();
Selim Cinek1f3f5442015-04-10 17:54:46 -0700652 if (headsUpChild == null) {
653 headsUpChild = mPrivateLayout.getContractedChild();
Selim Cinek8d490d42015-04-10 00:05:50 -0700654 }
Selim Cinek1f3f5442015-04-10 17:54:46 -0700655 mHeadsUpHeight = headsUpChild.getHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +0200656 if (intrinsicBefore != getIntrinsicHeight()) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100657 notifyHeightChanged(false /* needsAnimation */);
Selim Cinekd2319fb2014-09-01 19:41:54 +0200658 }
659 }
660
Jorim Jaggiae441282014-08-01 02:45:18 +0200661 public void setSensitive(boolean sensitive) {
662 mSensitive = sensitive;
663 }
664
665 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
Selim Cinek60122be2015-04-15 18:16:50 -0700666 mHideSensitiveForIntrinsicHeight = hideSensitive;
Jorim Jaggiae441282014-08-01 02:45:18 +0200667 }
668
669 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
670 long duration) {
671 boolean oldShowingPublic = mShowingPublic;
672 mShowingPublic = mSensitive && hideSensitive;
673 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
674 return;
675 }
Dan Sandlera5e0f412014-01-23 15:11:54 -0500676
677 // bail out if no public version
Selim Cinek1685e632014-04-08 02:27:49 +0200678 if (mPublicLayout.getChildCount() == 0) return;
Dan Sandlera5e0f412014-01-23 15:11:54 -0500679
Jorim Jaggiae441282014-08-01 02:45:18 +0200680 if (!animated) {
681 mPublicLayout.animate().cancel();
682 mPrivateLayout.animate().cancel();
683 mPublicLayout.setAlpha(1f);
684 mPrivateLayout.setAlpha(1f);
685 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
686 mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
687 } else {
688 animateShowingPublic(delay, duration);
689 }
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400690
691 updateVetoButton();
Jorim Jaggiae441282014-08-01 02:45:18 +0200692 mShowingPublicInitialized = true;
693 }
694
695 private void animateShowingPublic(long delay, long duration) {
696 final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
697 View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
698 source.setVisibility(View.VISIBLE);
699 target.setVisibility(View.VISIBLE);
700 target.setAlpha(0f);
701 source.animate().cancel();
702 target.animate().cancel();
703 source.animate()
704 .alpha(0f)
Jorim Jaggiae441282014-08-01 02:45:18 +0200705 .setStartDelay(delay)
706 .setDuration(duration)
707 .withEndAction(new Runnable() {
708 @Override
709 public void run() {
710 source.setVisibility(View.INVISIBLE);
711 }
712 });
713 target.animate()
714 .alpha(1f)
Jorim Jaggiae441282014-08-01 02:45:18 +0200715 .setStartDelay(delay)
716 .setDuration(duration);
Dan Sandler0d3e62f2014-07-14 17:13:50 -0400717 }
718
719 private void updateVetoButton() {
720 // public versions cannot be dismissed
721 mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
Dan Sandlera5e0f412014-01-23 15:11:54 -0500722 }
Jorim Jaggi251957d2014-04-09 04:24:09 +0200723
Selim Cinekb5605e52015-02-20 18:21:41 +0100724 public void setChildrenExpanded(boolean expanded, boolean animate) {
725 mChildrenExpanded = expanded;
726 updateChildrenVisibility(animate);
727 }
728
729 public void updateExpandButton() {
730 boolean hasExpand = hasBottomDecor();
731 if (hasExpand != mHasExpandAction) {
732 if (hasExpand) {
733 if (mExpandButtonContainer == null) {
734 mExpandButtonStub.inflate();
735 }
736 mExpandButtonContainer.setVisibility(View.VISIBLE);
737 updateExpandButtonAppearance();
738 updateExpandButtonColor();
739 } else if (mExpandButtonContainer != null) {
740 mExpandButtonContainer.setVisibility(View.GONE);
741 }
742 notifyHeightChanged(true /* needsAnimation */);
743 }
744 mHasExpandAction = hasExpand;
745 }
746
747 private void updateExpandButtonAppearance() {
748 if (mExpandButtonContainer == null) {
749 return;
750 }
751 float expandButtonAlpha = 0.0f;
752 float expandButtonTranslation = 0.0f;
753 float containerTranslation = 0.0f;
754 int minHeight = getMinHeight();
755 if (!mChildrenExpanded || mChildExpandAnimator != null) {
756 int expandActionHeight = getBottomDecorHeight();
757 int translationY = getActualHeight() - expandActionHeight;
758 if (translationY > minHeight) {
759 containerTranslation = translationY;
760 expandButtonAlpha = 1.0f;
761 expandButtonTranslation = 0.0f;
762 } else {
763 containerTranslation = minHeight;
764 float progress = expandActionHeight != 0
765 ? (minHeight - translationY) / (float) expandActionHeight
766 : 1.0f;
767 expandButtonTranslation = -progress * expandActionHeight * 0.7f;
768 float alphaProgress = Math.min(progress / 0.7f, 1.0f);
769 alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
770 expandButtonAlpha = 1.0f - alphaProgress;
771 }
772 }
773 if (mChildExpandAnimator != null || mChildrenExpanded) {
774 expandButtonAlpha = (1.0f - mChildrenExpandProgress)
775 * expandButtonAlpha;
776 expandButtonTranslation = (1.0f - mChildrenExpandProgress)
777 * expandButtonTranslation;
778 float newTranslation = -getBottomDecorHeight();
779
780 // We don't want to take the actual height of the view as this is already
781 // interpolated by a custom interpolator leading to a confusing animation. We want
782 // to have a stable end value to interpolate in between
783 float collapsedHeight = !mChildrenExpanded
784 ? Math.max(StackStateAnimator.getFinalActualHeight(this)
785 - getBottomDecorHeight(), minHeight)
786 : mExpandButtonStart;
787 float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
788 mChildrenExpandProgress);
789 containerTranslation = (1.0f - translationProgress) * collapsedHeight
790 + translationProgress * newTranslation;
791 }
792 mExpandButton.setAlpha(expandButtonAlpha);
793 mExpandButtonDivider.setAlpha(expandButtonAlpha);
794 mExpandButton.setTranslationY(expandButtonTranslation);
795 mExpandButtonContainer.setTranslationY(containerTranslation);
796 NotificationContentView showingLayout = getShowingLayout();
797 float layoutTranslation =
798 mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
799 layoutTranslation = Math.min(layoutTranslation, 0);
800 if (!mChildrenExpanded && mChildExpandAnimator == null) {
801 // Needed for the DragDownHelper in order not to jump there, as the position
802 // can be negative for a short time.
803 layoutTranslation = 0;
804 }
805 showingLayout.setTranslationY(layoutTranslation);
806 if (mChildrenContainer != null) {
807 mChildrenContainer.setTranslationY(
808 mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
809 }
810 }
811
812 private void updateExpandButtonColor() {
813 // TODO: This needs some more baking, currently only the divider is colored according to
814 // the tint, but legacy black doesn't work yet perfectly for the button etc.
815 int color = getRippleColor();
816 if (color == mNormalRippleColor) {
817 color = 0;
818 }
819 if (mExpandButtonDivider != null) {
820 applyTint(mExpandButtonDivider, color);
821 }
822 if (mChildrenContainer != null) {
823 mChildrenContainer.setTintColor(color);
824 }
825 }
826
827 public static void applyTint(View v, int color) {
828 int alpha;
829 if (color != 0) {
830 alpha = COLORED_DIVIDER_ALPHA;
831 } else {
832 color = 0xff000000;
833 alpha = DEFAULT_DIVIDER_ALPHA;
834 }
835 if (v.getBackground() instanceof ColorDrawable) {
836 ColorDrawable background = (ColorDrawable) v.getBackground();
837 background.mutate();
838 background.setColor(color);
839 background.setAlpha(alpha);
840 }
841 }
842
Selim Cinek1685e632014-04-08 02:27:49 +0200843 public int getMaxExpandHeight() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100844 return mMaxExpandHeight;
Chris Wren51c75102013-07-16 20:49:17 -0400845 }
Jorim Jaggi584a7aa2014-04-10 23:26:13 +0200846
Jorim Jaggibe565df2014-04-28 17:51:23 +0200847 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200848 public boolean isContentExpandable() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200849 NotificationContentView showingLayout = getShowingLayout();
850 return showingLayout.isContentExpandable();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200851 }
852
853 @Override
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200854 public void setActualHeight(int height, boolean notifyListeners) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100855 super.setActualHeight(height, notifyListeners);
856 int contentHeight = calculateContentHeightFromActualHeight(height);
857 mPrivateLayout.setContentHeight(contentHeight);
858 mPublicLayout.setContentHeight(contentHeight);
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200859 if (mGuts != null) {
860 mGuts.setActualHeight(height);
861 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200862 invalidate();
Selim Cinekb5605e52015-02-20 18:21:41 +0100863 updateExpandButtonAppearance();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200864 }
865
866 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +0100867 public int getMaxContentHeight() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200868 NotificationContentView showingLayout = getShowingLayout();
869 return showingLayout.getMaxHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200870 }
871
872 @Override
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200873 public int getMinHeight() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200874 NotificationContentView showingLayout = getShowingLayout();
875 return showingLayout.getMinHeight();
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200876 }
877
878 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200879 public void setClipTopAmount(int clipTopAmount) {
880 super.setClipTopAmount(clipTopAmount);
881 mPrivateLayout.setClipTopAmount(clipTopAmount);
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200882 mPublicLayout.setClipTopAmount(clipTopAmount);
Jorim Jaggib1cd3c12014-09-08 19:55:17 +0200883 if (mGuts != null) {
884 mGuts.setClipTopAmount(clipTopAmount);
885 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200886 }
887
888 public void notifyContentUpdated() {
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200889 mPublicLayout.notifyContentUpdated();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200890 mPrivateLayout.notifyContentUpdated();
Selim Cinek343e6e22014-04-11 21:23:30 +0200891 }
Selim Cinek7d447722014-06-10 15:51:59 +0200892
Selim Cinek31094df2014-08-14 19:28:15 +0200893 public boolean isMaxExpandHeightInitialized() {
894 return mMaxExpandHeight != 0;
Selim Cinek7d447722014-06-10 15:51:59 +0200895 }
Selim Cinek2f0df8a2014-06-10 17:40:42 +0200896
897 private NotificationContentView getShowingLayout() {
898 return mShowingPublic ? mPublicLayout : mPrivateLayout;
899 }
Chris Wren78403d72014-07-28 10:23:24 +0100900
901 public void setExpansionLogger(ExpansionLogger logger, String key) {
902 mLogger = logger;
903 mLoggingKey = key;
904 }
905
906
907 private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
908 final boolean nowExpanded = isExpanded();
909 if (wasExpanded != nowExpanded && mLogger != null) {
910 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
911 }
912 }
Chris Wren51c75102013-07-16 20:49:17 -0400913}