• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.statusbar;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.graphics.drawable.AnimatedVectorDrawable;
24 import android.graphics.drawable.AnimationDrawable;
25 import android.graphics.drawable.ColorDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.RippleDrawable;
28 import android.service.notification.StatusBarNotification;
29 import android.util.AttributeSet;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.view.ViewStub;
33 import android.view.accessibility.AccessibilityEvent;
34 import android.view.animation.LinearInterpolator;
35 import android.widget.Chronometer;
36 import android.widget.ImageView;
37 
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.phone.NotificationGroupManager;
40 import com.android.systemui.statusbar.phone.PhoneStatusBar;
41 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
42 import com.android.systemui.statusbar.stack.StackScrollState;
43 import com.android.systemui.statusbar.stack.StackStateAnimator;
44 import com.android.systemui.statusbar.stack.StackViewState;
45 
46 import java.util.List;
47 
48 public class ExpandableNotificationRow extends ActivatableNotificationView {
49 
50     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
51     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
52     private final LinearInterpolator mLinearInterpolator = new LinearInterpolator();
53     private int mRowMinHeight;
54 
55     /** Does this row contain layouts that can adapt to row expansion */
56     private boolean mExpandable;
57     /** Has the user actively changed the expansion state of this row */
58     private boolean mHasUserChangedExpansion;
59     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
60     private boolean mUserExpanded;
61     /** Is the user touching this row */
62     private boolean mUserLocked;
63     /** Are we showing the "public" version */
64     private boolean mShowingPublic;
65     private boolean mSensitive;
66     private boolean mShowingPublicInitialized;
67     private boolean mHideSensitiveForIntrinsicHeight;
68 
69     /**
70      * Is this notification expanded by the system. The expansion state can be overridden by the
71      * user expansion.
72      */
73     private boolean mIsSystemExpanded;
74 
75     /**
76      * Whether the notification expansion is disabled. This is the case on Keyguard.
77      */
78     private boolean mExpansionDisabled;
79 
80     private NotificationContentView mPublicLayout;
81     private NotificationContentView mPrivateLayout;
82     private int mMaxExpandHeight;
83     private int mHeadsUpHeight;
84     private View mVetoButton;
85     private boolean mClearable;
86     private ExpansionLogger mLogger;
87     private String mLoggingKey;
88     private boolean mWasReset;
89     private NotificationGuts mGuts;
90     private StatusBarNotification mStatusBarNotification;
91     private boolean mIsHeadsUp;
92     private boolean mLastChronometerRunning = true;
93     private View mExpandButton;
94     private View mExpandButtonDivider;
95     private ViewStub mExpandButtonStub;
96     private ViewStub mChildrenContainerStub;
97     private NotificationGroupManager mGroupManager;
98     private View mExpandButtonContainer;
99     private boolean mChildrenExpanded;
100     private NotificationChildrenContainer mChildrenContainer;
101     private ValueAnimator mChildExpandAnimator;
102     private float mChildrenExpandProgress;
103     private float mExpandButtonStart;
104     private ViewStub mGutsStub;
105     private boolean mHasExpandAction;
106     private boolean mIsSystemChildExpanded;
107     private boolean mIsPinned;
108     private OnClickListener mExpandClickListener = new OnClickListener() {
109         @Override
110         public void onClick(View v) {
111             mGroupManager.setGroupExpanded(mStatusBarNotification,
112                     !mChildrenExpanded);
113         }
114     };
115 
116     private boolean mJustClicked;
117 
getPrivateLayout()118     public NotificationContentView getPrivateLayout() {
119         return mPrivateLayout;
120     }
121 
getPublicLayout()122     public NotificationContentView getPublicLayout() {
123         return mPublicLayout;
124     }
125 
setIconAnimationRunning(boolean running)126     public void setIconAnimationRunning(boolean running) {
127         setIconAnimationRunning(running, mPublicLayout);
128         setIconAnimationRunning(running, mPrivateLayout);
129     }
130 
setIconAnimationRunning(boolean running, NotificationContentView layout)131     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
132         if (layout != null) {
133             View contractedChild = layout.getContractedChild();
134             View expandedChild = layout.getExpandedChild();
135             View headsUpChild = layout.getHeadsUpChild();
136             setIconAnimationRunningForChild(running, contractedChild);
137             setIconAnimationRunningForChild(running, expandedChild);
138             setIconAnimationRunningForChild(running, headsUpChild);
139         }
140     }
141 
setIconAnimationRunningForChild(boolean running, View child)142     private void setIconAnimationRunningForChild(boolean running, View child) {
143         if (child != null) {
144             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
145             setIconRunning(icon, running);
146             ImageView rightIcon = (ImageView) child.findViewById(
147                     com.android.internal.R.id.right_icon);
148             setIconRunning(rightIcon, running);
149         }
150     }
151 
setIconRunning(ImageView imageView, boolean running)152     private void setIconRunning(ImageView imageView, boolean running) {
153         if (imageView != null) {
154             Drawable drawable = imageView.getDrawable();
155             if (drawable instanceof AnimationDrawable) {
156                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
157                 if (running) {
158                     animationDrawable.start();
159                 } else {
160                     animationDrawable.stop();
161                 }
162             } else if (drawable instanceof AnimatedVectorDrawable) {
163                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
164                 if (running) {
165                     animationDrawable.start();
166                 } else {
167                     animationDrawable.stop();
168                 }
169             }
170         }
171     }
172 
setStatusBarNotification(StatusBarNotification statusBarNotification)173     public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
174         mStatusBarNotification = statusBarNotification;
175         updateVetoButton();
176         updateExpandButton();
177     }
178 
getStatusBarNotification()179     public StatusBarNotification getStatusBarNotification() {
180         return mStatusBarNotification;
181     }
182 
isHeadsUp()183     public boolean isHeadsUp() {
184         return mIsHeadsUp;
185     }
186 
setHeadsUp(boolean isHeadsUp)187     public void setHeadsUp(boolean isHeadsUp) {
188         int intrinsicBefore = getIntrinsicHeight();
189         mIsHeadsUp = isHeadsUp;
190         mPrivateLayout.setHeadsUp(isHeadsUp);
191         if (intrinsicBefore != getIntrinsicHeight()) {
192             notifyHeightChanged(false  /* needsAnimation */);
193         }
194     }
195 
setGroupManager(NotificationGroupManager groupManager)196     public void setGroupManager(NotificationGroupManager groupManager) {
197         mGroupManager = groupManager;
198     }
199 
addChildNotification(ExpandableNotificationRow row)200     public void addChildNotification(ExpandableNotificationRow row) {
201         addChildNotification(row, -1);
202     }
203 
204     /**
205      * Add a child notification to this view.
206      *
207      * @param row the row to add
208      * @param childIndex the index to add it at, if -1 it will be added at the end
209      */
addChildNotification(ExpandableNotificationRow row, int childIndex)210     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
211         if (mChildrenContainer == null) {
212             mChildrenContainerStub.inflate();
213         }
214         mChildrenContainer.addNotification(row, childIndex);
215     }
216 
removeChildNotification(ExpandableNotificationRow row)217     public void removeChildNotification(ExpandableNotificationRow row) {
218         if (mChildrenContainer != null) {
219             mChildrenContainer.removeNotification(row);
220         }
221     }
222 
223     @Override
areChildrenExpanded()224     public boolean areChildrenExpanded() {
225         return mChildrenExpanded;
226     }
227 
getNotificationChildren()228     public List<ExpandableNotificationRow> getNotificationChildren() {
229         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
230     }
231 
232     /**
233      * Apply the order given in the list to the children.
234      *
235      * @param childOrder the new list order
236      * @return whether the list order has changed
237      */
applyChildOrder(List<ExpandableNotificationRow> childOrder)238     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
239         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
240     }
241 
getChildrenStates(StackScrollState resultState)242     public void getChildrenStates(StackScrollState resultState) {
243         if (mChildrenExpanded) {
244             StackViewState parentState = resultState.getViewStateForView(this);
245             mChildrenContainer.getState(resultState, parentState);
246         }
247     }
248 
applyChildrenState(StackScrollState state)249     public void applyChildrenState(StackScrollState state) {
250         if (mChildrenExpanded) {
251             mChildrenContainer.applyState(state);
252         }
253     }
254 
prepareExpansionChanged(StackScrollState state)255     public void prepareExpansionChanged(StackScrollState state) {
256         if (mChildrenExpanded) {
257             mChildrenContainer.prepareExpansionChanged(state);
258         }
259     }
260 
startChildAnimation(StackScrollState finalState, StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration)261     public void startChildAnimation(StackScrollState finalState,
262             StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) {
263         if (mChildrenExpanded) {
264             mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay,
265                     duration);
266         }
267     }
268 
getViewAtPosition(float y)269     public ExpandableNotificationRow getViewAtPosition(float y) {
270         if (!mChildrenExpanded) {
271             return this;
272         } else {
273             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
274             return view == null ? this : view;
275         }
276     }
277 
getGuts()278     public NotificationGuts getGuts() {
279         return mGuts;
280     }
281 
calculateContentHeightFromActualHeight(int actualHeight)282     protected int calculateContentHeightFromActualHeight(int actualHeight) {
283         int realActualHeight = actualHeight;
284         if (hasBottomDecor()) {
285             realActualHeight -= getBottomDecorHeight();
286         }
287         realActualHeight = Math.max(getMinHeight(), realActualHeight);
288         return realActualHeight;
289     }
290 
291     /**
292      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
293      * the notification will be rendered on top of the screen.
294      *
295      * @param pinned whether it is pinned
296      */
setPinned(boolean pinned)297     public void setPinned(boolean pinned) {
298         mIsPinned = pinned;
299         setChronometerRunning(mLastChronometerRunning);
300     }
301 
isPinned()302     public boolean isPinned() {
303         return mIsPinned;
304     }
305 
getHeadsUpHeight()306     public int getHeadsUpHeight() {
307         return mHeadsUpHeight;
308     }
309 
310     /**
311      * Mark whether this notification was just clicked, i.e. the user has just clicked this
312      * notification in this frame.
313      */
setJustClicked(boolean justClicked)314     public void setJustClicked(boolean justClicked) {
315         mJustClicked = justClicked;
316     }
317 
318     /**
319      * @return true if this notification has been clicked in this frame, false otherwise
320      */
wasJustClicked()321     public boolean wasJustClicked() {
322         return mJustClicked;
323     }
324 
setChronometerRunning(boolean running)325     public void setChronometerRunning(boolean running) {
326         mLastChronometerRunning = running;
327         setChronometerRunning(running, mPrivateLayout);
328         setChronometerRunning(running, mPublicLayout);
329         if (mChildrenContainer != null) {
330             List<ExpandableNotificationRow> notificationChildren =
331                     mChildrenContainer.getNotificationChildren();
332             for (int i = 0; i < notificationChildren.size(); i++) {
333                 ExpandableNotificationRow child = notificationChildren.get(i);
334                 child.setChronometerRunning(running);
335             }
336         }
337     }
338 
setChronometerRunning(boolean running, NotificationContentView layout)339     private void setChronometerRunning(boolean running, NotificationContentView layout) {
340         if (layout != null) {
341             running = running || isPinned();
342             View contractedChild = layout.getContractedChild();
343             View expandedChild = layout.getExpandedChild();
344             View headsUpChild = layout.getHeadsUpChild();
345             setChronometerRunningForChild(running, contractedChild);
346             setChronometerRunningForChild(running, expandedChild);
347             setChronometerRunningForChild(running, headsUpChild);
348         }
349     }
350 
setChronometerRunningForChild(boolean running, View child)351     private void setChronometerRunningForChild(boolean running, View child) {
352         if (child != null) {
353             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
354             if (chronometer instanceof Chronometer) {
355                 ((Chronometer) chronometer).setStarted(running);
356             }
357         }
358     }
359 
360     public interface ExpansionLogger {
logNotificationExpansion(String key, boolean userAction, boolean expanded)361         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
362     }
363 
ExpandableNotificationRow(Context context, AttributeSet attrs)364     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
365         super(context, attrs);
366     }
367 
368     /**
369      * Resets this view so it can be re-used for an updated notification.
370      */
371     @Override
reset()372     public void reset() {
373         super.reset();
374         mRowMinHeight = 0;
375         final boolean wasExpanded = isExpanded();
376         mMaxViewHeight = 0;
377         mExpandable = false;
378         mHasUserChangedExpansion = false;
379         mUserLocked = false;
380         mShowingPublic = false;
381         mSensitive = false;
382         mShowingPublicInitialized = false;
383         mIsSystemExpanded = false;
384         mExpansionDisabled = false;
385         mPublicLayout.reset(mIsHeadsUp);
386         mPrivateLayout.reset(mIsHeadsUp);
387         resetHeight();
388         logExpansionEvent(false, wasExpanded);
389     }
390 
resetHeight()391     public void resetHeight() {
392         if (mIsHeadsUp) {
393             resetActualHeight();
394         }
395         mMaxExpandHeight = 0;
396         mHeadsUpHeight = 0;
397         mWasReset = true;
398         onHeightReset();
399         requestLayout();
400     }
401 
402     @Override
filterMotionEvent(MotionEvent event)403     protected boolean filterMotionEvent(MotionEvent event) {
404         return mIsHeadsUp || super.filterMotionEvent(event);
405     }
406 
407     @Override
onFinishInflate()408     protected void onFinishInflate() {
409         super.onFinishInflate();
410         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
411         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
412         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
413         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
414             @Override
415             public void onInflate(ViewStub stub, View inflated) {
416                 mGuts = (NotificationGuts) inflated;
417                 mGuts.setClipTopAmount(getClipTopAmount());
418                 mGuts.setActualHeight(getActualHeight());
419                 mGutsStub = null;
420             }
421         });
422         mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub);
423         mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() {
424 
425             @Override
426             public void onInflate(ViewStub stub, View inflated) {
427                 mExpandButtonContainer = inflated;
428                 mExpandButton = inflated.findViewById(R.id.notification_expand_button);
429                 mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider);
430                 mExpandButtonContainer.setOnClickListener(mExpandClickListener);
431             }
432         });
433         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
434         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
435 
436             @Override
437             public void onInflate(ViewStub stub, View inflated) {
438                 mChildrenContainer = (NotificationChildrenContainer) inflated;
439                 mChildrenContainer.setCollapseClickListener(mExpandClickListener);
440                 updateChildrenVisibility(false);
441             }
442         });
443         mVetoButton = findViewById(R.id.veto);
444     }
445 
inflateGuts()446     public void inflateGuts() {
447         if (mGuts == null) {
448             mGutsStub.inflate();
449         }
450     }
451 
updateChildrenVisibility(boolean animated)452     private void updateChildrenVisibility(boolean animated) {
453         if (mChildrenContainer == null) {
454             return;
455         }
456         if (mChildExpandAnimator != null) {
457             mChildExpandAnimator.cancel();
458         }
459         float targetProgress = mChildrenExpanded ? 1.0f : 0.0f;
460         if (animated) {
461             if (mChildrenExpanded) {
462                 mChildrenContainer.setVisibility(VISIBLE);
463             }
464             mExpandButtonStart = mExpandButtonContainer.getTranslationY();
465             mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress);
466             mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
467                 @Override
468                 public void onAnimationUpdate(ValueAnimator animation) {
469                     setChildrenExpandProgress((float) animation.getAnimatedValue());
470                 }
471             });
472             mChildExpandAnimator.addListener(new AnimatorListenerAdapter() {
473                 @Override
474                 public void onAnimationEnd(Animator animation) {
475                     mChildExpandAnimator = null;
476                     if (!mChildrenExpanded) {
477                         mChildrenContainer.setVisibility(INVISIBLE);
478                     }
479                 }
480             });
481             mChildExpandAnimator.setInterpolator(mLinearInterpolator);
482             mChildExpandAnimator.setDuration(
483                     StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED);
484             mChildExpandAnimator.start();
485         } else {
486             setChildrenExpandProgress(targetProgress);
487             mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE);
488         }
489     }
490 
setChildrenExpandProgress(float progress)491     private void setChildrenExpandProgress(float progress) {
492         mChildrenExpandProgress = progress;
493         updateExpandButtonAppearance();
494         NotificationContentView showingLayout = getShowingLayout();
495         float alpha = 1.0f - mChildrenExpandProgress;
496         alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha);
497         showingLayout.setAlpha(alpha);
498     }
499 
500     @Override
onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event)501     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
502         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
503             // Add a record for the entire layout since its content is somehow small.
504             // The event comes from a leaf view that is interacted with.
505             AccessibilityEvent record = AccessibilityEvent.obtain();
506             onInitializeAccessibilityEvent(record);
507             dispatchPopulateAccessibilityEvent(record);
508             event.appendRecord(record);
509             return true;
510         }
511         return false;
512     }
513 
514     @Override
setDark(boolean dark, boolean fade, long delay)515     public void setDark(boolean dark, boolean fade, long delay) {
516         super.setDark(dark, fade, delay);
517         final NotificationContentView showing = getShowingLayout();
518         if (showing != null) {
519             showing.setDark(dark, fade, delay);
520         }
521     }
522 
setHeightRange(int rowMinHeight, int rowMaxHeight)523     public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
524         mRowMinHeight = rowMinHeight;
525         mMaxViewHeight = rowMaxHeight;
526     }
527 
isExpandable()528     public boolean isExpandable() {
529         return mExpandable;
530     }
531 
setExpandable(boolean expandable)532     public void setExpandable(boolean expandable) {
533         mExpandable = expandable;
534     }
535 
536     /**
537      * @return whether the user has changed the expansion state
538      */
hasUserChangedExpansion()539     public boolean hasUserChangedExpansion() {
540         return mHasUserChangedExpansion;
541     }
542 
isUserExpanded()543     public boolean isUserExpanded() {
544         return mUserExpanded;
545     }
546 
547     /**
548      * Set this notification to be expanded by the user
549      *
550      * @param userExpanded whether the user wants this notification to be expanded
551      */
setUserExpanded(boolean userExpanded)552     public void setUserExpanded(boolean userExpanded) {
553         if (userExpanded && !mExpandable) return;
554         final boolean wasExpanded = isExpanded();
555         mHasUserChangedExpansion = true;
556         mUserExpanded = userExpanded;
557         logExpansionEvent(true, wasExpanded);
558     }
559 
resetUserExpansion()560     public void resetUserExpansion() {
561         mHasUserChangedExpansion = false;
562         mUserExpanded = false;
563     }
564 
isUserLocked()565     public boolean isUserLocked() {
566         return mUserLocked;
567     }
568 
setUserLocked(boolean userLocked)569     public void setUserLocked(boolean userLocked) {
570         mUserLocked = userLocked;
571     }
572 
573     /**
574      * @return has the system set this notification to be expanded
575      */
isSystemExpanded()576     public boolean isSystemExpanded() {
577         return mIsSystemExpanded;
578     }
579 
580     /**
581      * Set this notification to be expanded by the system.
582      *
583      * @param expand whether the system wants this notification to be expanded.
584      */
setSystemExpanded(boolean expand)585     public void setSystemExpanded(boolean expand) {
586         if (expand != mIsSystemExpanded) {
587             final boolean wasExpanded = isExpanded();
588             mIsSystemExpanded = expand;
589             notifyHeightChanged(false /* needsAnimation */);
590             logExpansionEvent(false, wasExpanded);
591         }
592     }
593 
594     /**
595      * @param expansionDisabled whether to prevent notification expansion
596      */
setExpansionDisabled(boolean expansionDisabled)597     public void setExpansionDisabled(boolean expansionDisabled) {
598         if (expansionDisabled != mExpansionDisabled) {
599             final boolean wasExpanded = isExpanded();
600             mExpansionDisabled = expansionDisabled;
601             logExpansionEvent(false, wasExpanded);
602             if (wasExpanded != isExpanded()) {
603                 notifyHeightChanged(false  /* needsAnimation */);
604             }
605         }
606     }
607 
608     /**
609      * @return Can the underlying notification be cleared?
610      */
isClearable()611     public boolean isClearable() {
612         return mStatusBarNotification != null && mStatusBarNotification.isClearable();
613     }
614 
615     /**
616      * Apply an expansion state to the layout.
617      */
applyExpansionToLayout()618     public void applyExpansionToLayout() {
619         boolean expand = isExpanded();
620         if (expand && mExpandable) {
621             setContentHeight(mMaxExpandHeight);
622         } else {
623             setContentHeight(mRowMinHeight);
624         }
625     }
626 
627     @Override
getIntrinsicHeight()628     public int getIntrinsicHeight() {
629         if (isUserLocked()) {
630             return getActualHeight();
631         }
632         boolean inExpansionState = isExpanded();
633         int maxContentHeight;
634         if (mSensitive && mHideSensitiveForIntrinsicHeight) {
635             return mRowMinHeight;
636         } else if (mIsHeadsUp) {
637             if (inExpansionState) {
638                 maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight);
639             } else {
640                 maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight);
641             }
642         } else if ((!inExpansionState && !mChildrenExpanded)) {
643             maxContentHeight = mRowMinHeight;
644         } else if (mChildrenExpanded) {
645             maxContentHeight = mChildrenContainer.getIntrinsicHeight();
646         } else {
647             maxContentHeight = getMaxExpandHeight();
648         }
649         return maxContentHeight + getBottomDecorHeight();
650     }
651 
652     @Override
hasBottomDecor()653     protected boolean hasBottomDecor() {
654         return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
655                 && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification);
656     }
657 
658     @Override
canHaveBottomDecor()659     protected boolean canHaveBottomDecor() {
660         return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp;
661     }
662 
663     /**
664      * Check whether the view state is currently expanded. This is given by the system in {@link
665      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
666      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
667      * view can differ from this state, if layout params are modified from outside.
668      *
669      * @return whether the view state is currently expanded.
670      */
isExpanded()671     private boolean isExpanded() {
672         return !mExpansionDisabled
673                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
674                 || isUserExpanded());
675     }
676 
isSystemChildExpanded()677     private boolean isSystemChildExpanded() {
678         return mIsSystemChildExpanded;
679     }
680 
setSystemChildExpanded(boolean expanded)681     public void setSystemChildExpanded(boolean expanded) {
682         mIsSystemChildExpanded = expanded;
683     }
684 
685     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)686     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
687         super.onLayout(changed, left, top, right, bottom);
688         boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
689         updateMaxHeights();
690         if (updateExpandHeight) {
691             applyExpansionToLayout();
692         }
693         mWasReset = false;
694     }
695 
696     @Override
isChildInvisible(View child)697     protected boolean isChildInvisible(View child) {
698 
699         // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the
700         // view will get too high and the shadows will be off.
701         boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp;
702         return super.isChildInvisible(child) || isInvisibleChildContainer;
703     }
704 
updateMaxHeights()705     private void updateMaxHeights() {
706         int intrinsicBefore = getIntrinsicHeight();
707         View expandedChild = mPrivateLayout.getExpandedChild();
708         if (expandedChild == null) {
709             expandedChild = mPrivateLayout.getContractedChild();
710         }
711         mMaxExpandHeight = expandedChild.getHeight();
712         View headsUpChild = mPrivateLayout.getHeadsUpChild();
713         if (headsUpChild == null) {
714             headsUpChild = mPrivateLayout.getContractedChild();
715         }
716         mHeadsUpHeight = headsUpChild.getHeight();
717         if (intrinsicBefore != getIntrinsicHeight()) {
718             notifyHeightChanged(false  /* needsAnimation */);
719         }
720     }
721 
setSensitive(boolean sensitive)722     public void setSensitive(boolean sensitive) {
723         mSensitive = sensitive;
724     }
725 
setHideSensitiveForIntrinsicHeight(boolean hideSensitive)726     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
727         mHideSensitiveForIntrinsicHeight = hideSensitive;
728     }
729 
setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)730     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
731             long duration) {
732         boolean oldShowingPublic = mShowingPublic;
733         mShowingPublic = mSensitive && hideSensitive;
734         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
735             return;
736         }
737 
738         // bail out if no public version
739         if (mPublicLayout.getChildCount() == 0) return;
740 
741         if (!animated) {
742             mPublicLayout.animate().cancel();
743             mPrivateLayout.animate().cancel();
744             mPublicLayout.setAlpha(1f);
745             mPrivateLayout.setAlpha(1f);
746             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
747             mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
748         } else {
749             animateShowingPublic(delay, duration);
750         }
751 
752         updateVetoButton();
753         mShowingPublicInitialized = true;
754     }
755 
animateShowingPublic(long delay, long duration)756     private void animateShowingPublic(long delay, long duration) {
757         final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
758         View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
759         source.setVisibility(View.VISIBLE);
760         target.setVisibility(View.VISIBLE);
761         target.setAlpha(0f);
762         source.animate().cancel();
763         target.animate().cancel();
764         source.animate()
765                 .alpha(0f)
766                 .setStartDelay(delay)
767                 .setDuration(duration)
768                 .withEndAction(new Runnable() {
769                     @Override
770                     public void run() {
771                         source.setVisibility(View.INVISIBLE);
772                     }
773                 });
774         target.animate()
775                 .alpha(1f)
776                 .setStartDelay(delay)
777                 .setDuration(duration);
778     }
779 
updateVetoButton()780     private void updateVetoButton() {
781         // public versions cannot be dismissed
782         mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
783     }
784 
setChildrenExpanded(boolean expanded, boolean animate)785     public void setChildrenExpanded(boolean expanded, boolean animate) {
786         mChildrenExpanded = expanded;
787         updateChildrenVisibility(animate);
788     }
789 
updateExpandButton()790     public void updateExpandButton() {
791         boolean hasExpand = hasBottomDecor();
792         if (hasExpand != mHasExpandAction) {
793             if (hasExpand) {
794                 if (mExpandButtonContainer == null) {
795                     mExpandButtonStub.inflate();
796                 }
797                 mExpandButtonContainer.setVisibility(View.VISIBLE);
798                 updateExpandButtonAppearance();
799                 updateExpandButtonColor();
800             } else if (mExpandButtonContainer != null) {
801                 mExpandButtonContainer.setVisibility(View.GONE);
802             }
803             notifyHeightChanged(true  /* needsAnimation */);
804         }
805         mHasExpandAction = hasExpand;
806     }
807 
updateExpandButtonAppearance()808     private void updateExpandButtonAppearance() {
809         if (mExpandButtonContainer == null) {
810             return;
811         }
812         float expandButtonAlpha = 0.0f;
813         float expandButtonTranslation = 0.0f;
814         float containerTranslation = 0.0f;
815         int minHeight = getMinHeight();
816         if (!mChildrenExpanded || mChildExpandAnimator != null) {
817             int expandActionHeight = getBottomDecorHeight();
818             int translationY = getActualHeight() - expandActionHeight;
819             if (translationY > minHeight) {
820                 containerTranslation = translationY;
821                 expandButtonAlpha = 1.0f;
822                 expandButtonTranslation = 0.0f;
823             } else {
824                 containerTranslation = minHeight;
825                 float progress = expandActionHeight != 0
826                         ? (minHeight - translationY) / (float) expandActionHeight
827                         : 1.0f;
828                 expandButtonTranslation = -progress * expandActionHeight * 0.7f;
829                 float alphaProgress = Math.min(progress / 0.7f, 1.0f);
830                 alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress);
831                 expandButtonAlpha = 1.0f - alphaProgress;
832             }
833         }
834         if (mChildExpandAnimator != null || mChildrenExpanded) {
835             expandButtonAlpha = (1.0f - mChildrenExpandProgress)
836                     * expandButtonAlpha;
837             expandButtonTranslation = (1.0f - mChildrenExpandProgress)
838                     * expandButtonTranslation;
839             float newTranslation = -getBottomDecorHeight();
840 
841             // We don't want to take the actual height of the view as this is already
842             // interpolated by a custom interpolator leading to a confusing animation. We want
843             // to have a stable end value to interpolate in between
844             float collapsedHeight = !mChildrenExpanded
845                     ? Math.max(StackStateAnimator.getFinalActualHeight(this)
846                             - getBottomDecorHeight(), minHeight)
847                     : mExpandButtonStart;
848             float translationProgress = mFastOutSlowInInterpolator.getInterpolation(
849                     mChildrenExpandProgress);
850             containerTranslation = (1.0f - translationProgress) * collapsedHeight
851                     + translationProgress * newTranslation;
852         }
853         mExpandButton.setAlpha(expandButtonAlpha);
854         mExpandButtonDivider.setAlpha(expandButtonAlpha);
855         mExpandButton.setTranslationY(expandButtonTranslation);
856         mExpandButtonContainer.setTranslationY(containerTranslation);
857         NotificationContentView showingLayout = getShowingLayout();
858         float layoutTranslation =
859                 mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight();
860         layoutTranslation = Math.min(layoutTranslation, 0);
861         if (!mChildrenExpanded && mChildExpandAnimator == null) {
862             // Needed for the DragDownHelper in order not to jump there, as the position
863             // can be negative for a short time.
864             layoutTranslation = 0;
865         }
866         showingLayout.setTranslationY(layoutTranslation);
867         if (mChildrenContainer != null) {
868             mChildrenContainer.setTranslationY(
869                     mExpandButtonContainer.getTranslationY() + getBottomDecorHeight());
870         }
871     }
872 
updateExpandButtonColor()873     private void updateExpandButtonColor() {
874         // TODO: This needs some more baking, currently only the divider is colored according to
875         // the tint, but legacy black doesn't work yet perfectly for the button etc.
876         int color = getRippleColor();
877         if (color == mNormalRippleColor) {
878             color = 0;
879         }
880         if (mExpandButtonDivider != null) {
881             applyTint(mExpandButtonDivider, color);
882         }
883         if (mChildrenContainer != null) {
884             mChildrenContainer.setTintColor(color);
885         }
886     }
887 
applyTint(View v, int color)888     public static void applyTint(View v, int color) {
889         int alpha;
890         if (color != 0) {
891             alpha = COLORED_DIVIDER_ALPHA;
892         } else {
893             color = 0xff000000;
894             alpha = DEFAULT_DIVIDER_ALPHA;
895         }
896         if (v.getBackground() instanceof ColorDrawable) {
897             ColorDrawable background = (ColorDrawable) v.getBackground();
898             background.mutate();
899             background.setColor(color);
900             background.setAlpha(alpha);
901         }
902     }
903 
getMaxExpandHeight()904     public int getMaxExpandHeight() {
905         return mMaxExpandHeight;
906     }
907 
908     @Override
isContentExpandable()909     public boolean isContentExpandable() {
910         NotificationContentView showingLayout = getShowingLayout();
911         return showingLayout.isContentExpandable();
912     }
913 
914     @Override
getContentView()915     protected View getContentView() {
916         return getShowingLayout();
917     }
918 
919     @Override
setActualHeight(int height, boolean notifyListeners)920     public void setActualHeight(int height, boolean notifyListeners) {
921         super.setActualHeight(height, notifyListeners);
922         int contentHeight = calculateContentHeightFromActualHeight(height);
923         mPrivateLayout.setContentHeight(contentHeight);
924         mPublicLayout.setContentHeight(contentHeight);
925         if (mGuts != null) {
926             mGuts.setActualHeight(height);
927         }
928         invalidate();
929         updateExpandButtonAppearance();
930     }
931 
932     @Override
getMaxContentHeight()933     public int getMaxContentHeight() {
934         NotificationContentView showingLayout = getShowingLayout();
935         return showingLayout.getMaxHeight();
936     }
937 
938     @Override
getMinHeight()939     public int getMinHeight() {
940         NotificationContentView showingLayout = getShowingLayout();
941         return showingLayout.getMinHeight();
942     }
943 
944     @Override
setClipTopAmount(int clipTopAmount)945     public void setClipTopAmount(int clipTopAmount) {
946         super.setClipTopAmount(clipTopAmount);
947         mPrivateLayout.setClipTopAmount(clipTopAmount);
948         mPublicLayout.setClipTopAmount(clipTopAmount);
949         if (mGuts != null) {
950             mGuts.setClipTopAmount(clipTopAmount);
951         }
952     }
953 
notifyContentUpdated()954     public void notifyContentUpdated() {
955         mPublicLayout.notifyContentUpdated();
956         mPrivateLayout.notifyContentUpdated();
957     }
958 
isMaxExpandHeightInitialized()959     public boolean isMaxExpandHeightInitialized() {
960         return mMaxExpandHeight != 0;
961     }
962 
getShowingLayout()963     private NotificationContentView getShowingLayout() {
964         return mShowingPublic ? mPublicLayout : mPrivateLayout;
965     }
966 
967     @Override
setShowingLegacyBackground(boolean showing)968     public void setShowingLegacyBackground(boolean showing) {
969         super.setShowingLegacyBackground(showing);
970         mPrivateLayout.setShowingLegacyBackground(showing);
971         mPublicLayout.setShowingLegacyBackground(showing);
972     }
973 
setExpansionLogger(ExpansionLogger logger, String key)974     public void setExpansionLogger(ExpansionLogger logger, String key) {
975         mLogger = logger;
976         mLoggingKey = key;
977     }
978 
logExpansionEvent(boolean userAction, boolean wasExpanded)979     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
980         final boolean nowExpanded = isExpanded();
981         if (wasExpanded != nowExpanded && mLogger != null) {
982             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
983         }
984     }
985 }
986