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 static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ObjectAnimator;
24 import android.animation.ValueAnimator.AnimatorUpdateListener;
25 import android.annotation.Nullable;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.content.res.Configuration;
29 import android.graphics.drawable.AnimatedVectorDrawable;
30 import android.graphics.drawable.AnimationDrawable;
31 import android.graphics.drawable.ColorDrawable;
32 import android.graphics.drawable.Drawable;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.service.notification.StatusBarNotification;
36 import android.util.AttributeSet;
37 import android.util.FloatProperty;
38 import android.util.Property;
39 import android.view.LayoutInflater;
40 import android.view.MotionEvent;
41 import android.view.NotificationHeaderView;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.ViewStub;
45 import android.view.accessibility.AccessibilityEvent;
46 import android.view.accessibility.AccessibilityNodeInfo;
47 import android.widget.Chronometer;
48 import android.widget.FrameLayout;
49 import android.widget.ImageView;
50 import android.widget.RemoteViews;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.logging.MetricsLogger;
54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
55 import com.android.internal.util.NotificationColorUtil;
56 import com.android.internal.widget.CachingIconView;
57 import com.android.systemui.Dependency;
58 import com.android.systemui.Interpolators;
59 import com.android.systemui.R;
60 import com.android.systemui.classifier.FalsingManager;
61 import com.android.systemui.plugins.PluginListener;
62 import com.android.systemui.plugins.PluginManager;
63 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
64 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
65 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
66 import com.android.systemui.statusbar.notification.HybridNotificationView;
67 import com.android.systemui.statusbar.notification.NotificationInflater;
68 import com.android.systemui.statusbar.notification.NotificationUtils;
69 import com.android.systemui.statusbar.notification.VisualStabilityManager;
70 import com.android.systemui.statusbar.phone.NotificationGroupManager;
71 import com.android.systemui.statusbar.phone.StatusBar;
72 import com.android.systemui.statusbar.policy.HeadsUpManager;
73 import com.android.systemui.statusbar.stack.AnimationProperties;
74 import com.android.systemui.statusbar.stack.ExpandableViewState;
75 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
76 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
77 import com.android.systemui.statusbar.stack.StackScrollState;
78 
79 import java.util.ArrayList;
80 import java.util.List;
81 
82 public class ExpandableNotificationRow extends ActivatableNotificationView
83         implements PluginListener<NotificationMenuRowPlugin> {
84 
85     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
86     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
87     private static final int MENU_VIEW_INDEX = 0;
88 
89     public interface LayoutListener {
onLayout()90         public void onLayout();
91     }
92 
93     private LayoutListener mLayoutListener;
94     private boolean mLowPriorityStateUpdated;
95     private final NotificationInflater mNotificationInflater;
96     private int mIconTransformContentShift;
97     private int mIconTransformContentShiftNoIcon;
98     private int mNotificationMinHeightLegacy;
99     private int mMaxHeadsUpHeightLegacy;
100     private int mMaxHeadsUpHeight;
101     private int mMaxHeadsUpHeightIncreased;
102     private int mNotificationMinHeight;
103     private int mNotificationMinHeightLarge;
104     private int mNotificationMaxHeight;
105     private int mNotificationAmbientHeight;
106     private int mIncreasedPaddingBetweenElements;
107 
108     /** Does this row contain layouts that can adapt to row expansion */
109     private boolean mExpandable;
110     /** Has the user actively changed the expansion state of this row */
111     private boolean mHasUserChangedExpansion;
112     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
113     private boolean mUserExpanded;
114 
115     /**
116      * Has this notification been expanded while it was pinned
117      */
118     private boolean mExpandedWhenPinned;
119     /** Is the user touching this row */
120     private boolean mUserLocked;
121     /** Are we showing the "public" version */
122     private boolean mShowingPublic;
123     private boolean mSensitive;
124     private boolean mSensitiveHiddenInGeneral;
125     private boolean mShowingPublicInitialized;
126     private boolean mHideSensitiveForIntrinsicHeight;
127 
128     /**
129      * Is this notification expanded by the system. The expansion state can be overridden by the
130      * user expansion.
131      */
132     private boolean mIsSystemExpanded;
133 
134     /**
135      * Whether the notification is on the keyguard and the expansion is disabled.
136      */
137     private boolean mOnKeyguard;
138 
139     private Animator mTranslateAnim;
140     private ArrayList<View> mTranslateableViews;
141     private NotificationContentView mPublicLayout;
142     private NotificationContentView mPrivateLayout;
143     private NotificationContentView[] mLayouts;
144     private int mMaxExpandHeight;
145     private int mHeadsUpHeight;
146     private int mNotificationColor;
147     private ExpansionLogger mLogger;
148     private String mLoggingKey;
149     private NotificationGuts mGuts;
150     private NotificationData.Entry mEntry;
151     private StatusBarNotification mStatusBarNotification;
152     private String mAppName;
153     private boolean mIsHeadsUp;
154     private boolean mLastChronometerRunning = true;
155     private ViewStub mChildrenContainerStub;
156     private NotificationGroupManager mGroupManager;
157     private boolean mChildrenExpanded;
158     private boolean mIsSummaryWithChildren;
159     private NotificationChildrenContainer mChildrenContainer;
160     private NotificationMenuRowPlugin mMenuRow;
161     private ViewStub mGutsStub;
162     private boolean mIsSystemChildExpanded;
163     private boolean mIsPinned;
164     private FalsingManager mFalsingManager;
165     private HeadsUpManager mHeadsUpManager;
166 
167     private boolean mJustClicked;
168     private boolean mIconAnimationRunning;
169     private boolean mShowNoBackground;
170     private ExpandableNotificationRow mNotificationParent;
171     private OnExpandClickListener mOnExpandClickListener;
172     private boolean mGroupExpansionChanging;
173 
174     private OnClickListener mExpandClickListener = new OnClickListener() {
175         @Override
176         public void onClick(View v) {
177             if (!mShowingPublic && (!mIsLowPriority || isExpanded())
178                     && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
179                 mGroupExpansionChanging = true;
180                 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
181                 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification);
182                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
183                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
184                         nowExpanded);
185                 onExpansionChanged(true /* userAction */, wasExpanded);
186             } else {
187                 if (v.isAccessibilityFocused()) {
188                     mPrivateLayout.setFocusOnVisibilityChange();
189                 }
190                 boolean nowExpanded;
191                 if (isPinned()) {
192                     nowExpanded = !mExpandedWhenPinned;
193                     mExpandedWhenPinned = nowExpanded;
194                 } else {
195                     nowExpanded = !isExpanded();
196                     setUserExpanded(nowExpanded);
197                 }
198                 notifyHeightChanged(true);
199                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
200                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER,
201                         nowExpanded);
202             }
203         }
204     };
205     private boolean mForceUnlocked;
206     private boolean mDismissed;
207     private boolean mKeepInParent;
208     private boolean mRemoved;
209     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
210             new FloatProperty<ExpandableNotificationRow>("translate") {
211                 @Override
212                 public void setValue(ExpandableNotificationRow object, float value) {
213                     object.setTranslation(value);
214                 }
215 
216                 @Override
217                 public Float get(ExpandableNotificationRow object) {
218                     return object.getTranslation();
219                 }
220     };
221     private OnClickListener mOnClickListener;
222     private boolean mHeadsupDisappearRunning;
223     private View mChildAfterViewWhenDismissed;
224     private View mGroupParentWhenDismissed;
225     private boolean mRefocusOnDismiss;
226     private float mContentTransformationAmount;
227     private boolean mIconsVisible = true;
228     private boolean mAboveShelf;
229     private boolean mShowAmbient;
230     private boolean mIsLastChild;
231     private Runnable mOnDismissRunnable;
232     private boolean mIsLowPriority;
233     private boolean mIsColorized;
234     private boolean mUseIncreasedCollapsedHeight;
235     private boolean mUseIncreasedHeadsUpHeight;
236     private float mTranslationWhenRemoved;
237     private boolean mWasChildInGroupWhenRemoved;
238     private int mNotificationColorAmbient;
239 
240     @Override
isGroupExpansionChanging()241     public boolean isGroupExpansionChanging() {
242         if (isChildInGroup()) {
243             return mNotificationParent.isGroupExpansionChanging();
244         }
245         return mGroupExpansionChanging;
246     }
247 
setGroupExpansionChanging(boolean changing)248     public void setGroupExpansionChanging(boolean changing) {
249         mGroupExpansionChanging = changing;
250     }
251 
252     @Override
setActualHeightAnimating(boolean animating)253     public void setActualHeightAnimating(boolean animating) {
254         if (mPrivateLayout != null) {
255             mPrivateLayout.setContentHeightAnimating(animating);
256         }
257     }
258 
getPrivateLayout()259     public NotificationContentView getPrivateLayout() {
260         return mPrivateLayout;
261     }
262 
getPublicLayout()263     public NotificationContentView getPublicLayout() {
264         return mPublicLayout;
265     }
266 
setIconAnimationRunning(boolean running)267     public void setIconAnimationRunning(boolean running) {
268         for (NotificationContentView l : mLayouts) {
269             setIconAnimationRunning(running, l);
270         }
271         if (mIsSummaryWithChildren) {
272             setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
273             setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView());
274             List<ExpandableNotificationRow> notificationChildren =
275                     mChildrenContainer.getNotificationChildren();
276             for (int i = 0; i < notificationChildren.size(); i++) {
277                 ExpandableNotificationRow child = notificationChildren.get(i);
278                 child.setIconAnimationRunning(running);
279             }
280         }
281         mIconAnimationRunning = running;
282     }
283 
setIconAnimationRunning(boolean running, NotificationContentView layout)284     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
285         if (layout != null) {
286             View contractedChild = layout.getContractedChild();
287             View expandedChild = layout.getExpandedChild();
288             View headsUpChild = layout.getHeadsUpChild();
289             setIconAnimationRunningForChild(running, contractedChild);
290             setIconAnimationRunningForChild(running, expandedChild);
291             setIconAnimationRunningForChild(running, headsUpChild);
292         }
293     }
294 
setIconAnimationRunningForChild(boolean running, View child)295     private void setIconAnimationRunningForChild(boolean running, View child) {
296         if (child != null) {
297             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
298             setIconRunning(icon, running);
299             ImageView rightIcon = (ImageView) child.findViewById(
300                     com.android.internal.R.id.right_icon);
301             setIconRunning(rightIcon, running);
302         }
303     }
304 
setIconRunning(ImageView imageView, boolean running)305     private void setIconRunning(ImageView imageView, boolean running) {
306         if (imageView != null) {
307             Drawable drawable = imageView.getDrawable();
308             if (drawable instanceof AnimationDrawable) {
309                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
310                 if (running) {
311                     animationDrawable.start();
312                 } else {
313                     animationDrawable.stop();
314                 }
315             } else if (drawable instanceof AnimatedVectorDrawable) {
316                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
317                 if (running) {
318                     animationDrawable.start();
319                 } else {
320                     animationDrawable.stop();
321                 }
322             }
323         }
324     }
325 
updateNotification(NotificationData.Entry entry)326     public void updateNotification(NotificationData.Entry entry) {
327         mEntry = entry;
328         mStatusBarNotification = entry.notification;
329         mNotificationInflater.inflateNotificationViews();
330     }
331 
onNotificationUpdated()332     public void onNotificationUpdated() {
333         for (NotificationContentView l : mLayouts) {
334             l.onNotificationUpdated(mEntry);
335         }
336         mIsColorized = mStatusBarNotification.getNotification().isColorized();
337         mShowingPublicInitialized = false;
338         updateNotificationColor();
339         if (mMenuRow != null) {
340             mMenuRow.onNotificationUpdated();
341         }
342         if (mIsSummaryWithChildren) {
343             mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
344             mChildrenContainer.onNotificationUpdated();
345         }
346         if (mIconAnimationRunning) {
347             setIconAnimationRunning(true);
348         }
349         if (mNotificationParent != null) {
350             mNotificationParent.updateChildrenHeaderAppearance();
351         }
352         onChildrenCountChanged();
353         // The public layouts expand button is always visible
354         mPublicLayout.updateExpandButtons(true);
355         updateLimits();
356         updateIconVisibilities();
357         updateShelfIconColor();
358     }
359 
360     @VisibleForTesting
updateShelfIconColor()361     void updateShelfIconColor() {
362         StatusBarIconView expandedIcon = mEntry.expandedIcon;
363         boolean isPreL = Boolean.TRUE.equals(expandedIcon.getTag(R.id.icon_is_pre_L));
364         boolean colorize = !isPreL || NotificationUtils.isGrayscale(expandedIcon,
365                 NotificationColorUtil.getInstance(mContext));
366         int color = StatusBarIconView.NO_COLOR;
367         if (colorize) {
368             NotificationHeaderView header = getVisibleNotificationHeader();
369             if (header != null) {
370                 color = header.getOriginalIconColor();
371             } else {
372                 color = mEntry.getContrastedColor(mContext, mIsLowPriority && !isExpanded(),
373                         getBackgroundColorWithoutTint());
374             }
375         }
376         expandedIcon.setStaticDrawableColor(color);
377     }
378 
379     @Override
isDimmable()380     public boolean isDimmable() {
381         if (!getShowingLayout().isDimmable()) {
382             return false;
383         }
384         return super.isDimmable();
385     }
386 
updateLimits()387     private void updateLimits() {
388         for (NotificationContentView l : mLayouts) {
389             updateLimitsForView(l);
390         }
391     }
392 
updateLimitsForView(NotificationContentView layout)393     private void updateLimitsForView(NotificationContentView layout) {
394         boolean customView = layout.getContractedChild().getId()
395                 != com.android.internal.R.id.status_bar_latest_event_content;
396         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
397         int minHeight;
398         if (customView && beforeN && !mIsSummaryWithChildren) {
399             minHeight = mNotificationMinHeightLegacy;
400         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
401             minHeight = mNotificationMinHeightLarge;
402         } else {
403             minHeight = mNotificationMinHeight;
404         }
405         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
406                 layout.getHeadsUpChild().getId()
407                         != com.android.internal.R.id.status_bar_latest_event_content;
408         int headsUpheight;
409         if (headsUpCustom && beforeN) {
410             headsUpheight = mMaxHeadsUpHeightLegacy;
411         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
412             headsUpheight = mMaxHeadsUpHeightIncreased;
413         } else {
414             headsUpheight = mMaxHeadsUpHeight;
415         }
416         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
417                 mNotificationAmbientHeight);
418     }
419 
420     public StatusBarNotification getStatusBarNotification() {
421         return mStatusBarNotification;
422     }
423 
424     public NotificationData.Entry getEntry() {
425         return mEntry;
426     }
427 
428     public boolean isHeadsUp() {
429         return mIsHeadsUp;
430     }
431 
432     public void setHeadsUp(boolean isHeadsUp) {
433         int intrinsicBefore = getIntrinsicHeight();
434         mIsHeadsUp = isHeadsUp;
435         mPrivateLayout.setHeadsUp(isHeadsUp);
436         if (mIsSummaryWithChildren) {
437             // The overflow might change since we allow more lines as HUN.
438             mChildrenContainer.updateGroupOverflow();
439         }
440         if (intrinsicBefore != getIntrinsicHeight()) {
441             notifyHeightChanged(false  /* needsAnimation */);
442         }
443         if (isHeadsUp) {
444             setAboveShelf(true);
445         }
446     }
447 
448     public void setGroupManager(NotificationGroupManager groupManager) {
449         mGroupManager = groupManager;
450         mPrivateLayout.setGroupManager(groupManager);
451     }
452 
453     public void setRemoteInputController(RemoteInputController r) {
454         mPrivateLayout.setRemoteInputController(r);
455     }
456 
457     public void setAppName(String appName) {
458         mAppName = appName;
459         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
460             mMenuRow.setAppName(mAppName);
461         }
462     }
463 
464     public void addChildNotification(ExpandableNotificationRow row) {
465         addChildNotification(row, -1);
466     }
467 
468     /**
469      * Add a child notification to this view.
470      *
471      * @param row the row to add
472      * @param childIndex the index to add it at, if -1 it will be added at the end
473      */
474     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
475         if (mChildrenContainer == null) {
476             mChildrenContainerStub.inflate();
477         }
478         mChildrenContainer.addNotification(row, childIndex);
479         onChildrenCountChanged();
480         row.setIsChildInGroup(true, this);
481     }
482 
483     public void removeChildNotification(ExpandableNotificationRow row) {
484         if (mChildrenContainer != null) {
485             mChildrenContainer.removeNotification(row);
486         }
487         onChildrenCountChanged();
488         row.setIsChildInGroup(false, null);
489     }
490 
491     @Override
492     public boolean isChildInGroup() {
493         return mNotificationParent != null;
494     }
495 
496     public ExpandableNotificationRow getNotificationParent() {
497         return mNotificationParent;
498     }
499 
500     /**
501      * @param isChildInGroup Is this notification now in a group
502      * @param parent the new parent notification
503      */
504     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
505         boolean childInGroup = StatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
506         mNotificationParent = childInGroup ? parent : null;
507         mPrivateLayout.setIsChildInGroup(childInGroup);
508         mNotificationInflater.setIsChildInGroup(childInGroup);
509         resetBackgroundAlpha();
510         updateBackgroundForGroupState();
511         updateClickAndFocus();
512         if (mNotificationParent != null) {
513             setOverrideTintColor(NO_COLOR, 0.0f);
514             mNotificationParent.updateBackgroundForGroupState();
515         }
516         updateIconVisibilities();
517     }
518 
519     @Override
520     public boolean onTouchEvent(MotionEvent event) {
521         if (event.getActionMasked() != MotionEvent.ACTION_DOWN
522                 || !isChildInGroup() || isGroupExpanded()) {
523             return super.onTouchEvent(event);
524         } else {
525             return false;
526         }
527     }
528 
529     @Override
530     protected boolean handleSlideBack() {
531         if (mMenuRow != null && mMenuRow.isMenuVisible()) {
532             animateTranslateNotification(0 /* targetLeft */);
533             return true;
534         }
535         return false;
536     }
537 
538     @Override
539     protected boolean shouldHideBackground() {
540         return super.shouldHideBackground() || mShowNoBackground;
541     }
542 
543     @Override
544     public boolean isSummaryWithChildren() {
545         return mIsSummaryWithChildren;
546     }
547 
548     @Override
549     public boolean areChildrenExpanded() {
550         return mChildrenExpanded;
551     }
552 
553     public List<ExpandableNotificationRow> getNotificationChildren() {
554         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
555     }
556 
557     public int getNumberOfNotificationChildren() {
558         if (mChildrenContainer == null) {
559             return 0;
560         }
561         return mChildrenContainer.getNotificationChildren().size();
562     }
563 
564     /**
565      * Apply the order given in the list to the children.
566      *
567      * @param childOrder the new list order
568      * @param visualStabilityManager
569      * @param callback the callback to invoked in case it is not allowed
570      * @return whether the list order has changed
571      */
572     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
573             VisualStabilityManager visualStabilityManager,
574             VisualStabilityManager.Callback callback) {
575         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder,
576                 visualStabilityManager, callback);
577     }
578 
579     public void getChildrenStates(StackScrollState resultState) {
580         if (mIsSummaryWithChildren) {
581             ExpandableViewState parentState = resultState.getViewStateForView(this);
582             mChildrenContainer.getState(resultState, parentState);
583         }
584     }
585 
586     public void applyChildrenState(StackScrollState state) {
587         if (mIsSummaryWithChildren) {
588             mChildrenContainer.applyState(state);
589         }
590     }
591 
592     public void prepareExpansionChanged(StackScrollState state) {
593         if (mIsSummaryWithChildren) {
594             mChildrenContainer.prepareExpansionChanged(state);
595         }
596     }
597 
598     public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) {
599         if (mIsSummaryWithChildren) {
600             mChildrenContainer.startAnimationToState(finalState, properties);
601         }
602     }
603 
604     public ExpandableNotificationRow getViewAtPosition(float y) {
605         if (!mIsSummaryWithChildren || !mChildrenExpanded) {
606             return this;
607         } else {
608             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
609             return view == null ? this : view;
610         }
611     }
612 
613     public NotificationGuts getGuts() {
614         return mGuts;
615     }
616 
617     /**
618      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
619      * the notification will be rendered on top of the screen.
620      *
621      * @param pinned whether it is pinned
622      */
623     public void setPinned(boolean pinned) {
624         int intrinsicHeight = getIntrinsicHeight();
625         mIsPinned = pinned;
626         if (intrinsicHeight != getIntrinsicHeight()) {
627             notifyHeightChanged(false /* needsAnimation */);
628         }
629         if (pinned) {
630             setIconAnimationRunning(true);
631             mExpandedWhenPinned = false;
632         } else if (mExpandedWhenPinned) {
633             setUserExpanded(true);
634         }
635         setChronometerRunning(mLastChronometerRunning);
636     }
637 
638     public boolean isPinned() {
639         return mIsPinned;
640     }
641 
642     @Override
643     public int getPinnedHeadsUpHeight() {
644         return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
645     }
646 
647     /**
648      * @param atLeastMinHeight should the value returned be at least the minimum height.
649      *                         Used to avoid cyclic calls
650      * @return the height of the heads up notification when pinned
651      */
652     private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
653         if (mIsSummaryWithChildren) {
654             return mChildrenContainer.getIntrinsicHeight();
655         }
656         if(mExpandedWhenPinned) {
657             return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
658         } else if (atLeastMinHeight) {
659             return Math.max(getCollapsedHeight(), mHeadsUpHeight);
660         } else {
661             return mHeadsUpHeight;
662         }
663     }
664 
665     /**
666      * Mark whether this notification was just clicked, i.e. the user has just clicked this
667      * notification in this frame.
668      */
669     public void setJustClicked(boolean justClicked) {
670         mJustClicked = justClicked;
671     }
672 
673     /**
674      * @return true if this notification has been clicked in this frame, false otherwise
675      */
676     public boolean wasJustClicked() {
677         return mJustClicked;
678     }
679 
680     public void setChronometerRunning(boolean running) {
681         mLastChronometerRunning = running;
682         setChronometerRunning(running, mPrivateLayout);
683         setChronometerRunning(running, mPublicLayout);
684         if (mChildrenContainer != null) {
685             List<ExpandableNotificationRow> notificationChildren =
686                     mChildrenContainer.getNotificationChildren();
687             for (int i = 0; i < notificationChildren.size(); i++) {
688                 ExpandableNotificationRow child = notificationChildren.get(i);
689                 child.setChronometerRunning(running);
690             }
691         }
692     }
693 
694     private void setChronometerRunning(boolean running, NotificationContentView layout) {
695         if (layout != null) {
696             running = running || isPinned();
697             View contractedChild = layout.getContractedChild();
698             View expandedChild = layout.getExpandedChild();
699             View headsUpChild = layout.getHeadsUpChild();
700             setChronometerRunningForChild(running, contractedChild);
701             setChronometerRunningForChild(running, expandedChild);
702             setChronometerRunningForChild(running, headsUpChild);
703         }
704     }
705 
706     private void setChronometerRunningForChild(boolean running, View child) {
707         if (child != null) {
708             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
709             if (chronometer instanceof Chronometer) {
710                 ((Chronometer) chronometer).setStarted(running);
711             }
712         }
713     }
714 
715     public NotificationHeaderView getNotificationHeader() {
716         if (mIsSummaryWithChildren) {
717             return mChildrenContainer.getHeaderView();
718         }
719         return mPrivateLayout.getNotificationHeader();
720     }
721 
722     /**
723      * @return the currently visible notification header. This can be different from
724      * {@link #getNotificationHeader()} in case it is a low-priority group.
725      */
726     public NotificationHeaderView getVisibleNotificationHeader() {
727         if (mIsSummaryWithChildren && !mShowingPublic) {
728             return mChildrenContainer.getVisibleHeader();
729         }
730         return getShowingLayout().getVisibleNotificationHeader();
731     }
732 
733     public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
734         mOnExpandClickListener = onExpandClickListener;
735     }
736 
737     @Override
738     public void setOnClickListener(@Nullable OnClickListener l) {
739         super.setOnClickListener(l);
740         mOnClickListener = l;
741         updateClickAndFocus();
742     }
743 
744     private void updateClickAndFocus() {
745         boolean normalChild = !isChildInGroup() || isGroupExpanded();
746         boolean clickable = mOnClickListener != null && normalChild;
747         if (isFocusable() != normalChild) {
748             setFocusable(normalChild);
749         }
750         if (isClickable() != clickable) {
751             setClickable(clickable);
752         }
753     }
754 
755     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
756         mHeadsUpManager = headsUpManager;
757     }
758 
759     public void setGutsView(MenuItem item) {
760         if (mGuts != null && item.getGutsView() instanceof GutsContent) {
761             ((GutsContent) item.getGutsView()).setGutsParent(mGuts);
762             mGuts.setGutsContent((GutsContent) item.getGutsView());
763         }
764     }
765 
766     @Override
767     protected void onAttachedToWindow() {
768         super.onAttachedToWindow();
769         Dependency.get(PluginManager.class).addPluginListener(this,
770                 NotificationMenuRowPlugin.class, false /* Allow multiple */);
771     }
772 
773     @Override
774     protected void onDetachedFromWindow() {
775         super.onDetachedFromWindow();
776         Dependency.get(PluginManager.class).removePluginListener(this);
777     }
778 
779     @Override
780     public void onPluginConnected(NotificationMenuRowPlugin plugin, Context pluginContext) {
781         boolean existed = mMenuRow.getMenuView() != null;
782         if (existed) {
783             removeView(mMenuRow.getMenuView());
784         }
785         mMenuRow = plugin;
786         if (mMenuRow.useDefaultMenuItems()) {
787             ArrayList<MenuItem> items = new ArrayList<>();
788             items.add(NotificationMenuRow.createInfoItem(mContext));
789             items.add(NotificationMenuRow.createSnoozeItem(mContext));
790             mMenuRow.setMenuItems(items);
791         }
792         if (existed) {
793             createMenu();
794         }
795     }
796 
797     @Override
798     public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
799         boolean existed = mMenuRow.getMenuView() != null;
800         mMenuRow = new NotificationMenuRow(mContext); // Back to default
801         if (existed) {
802             createMenu();
803         }
804     }
805 
806     public NotificationMenuRowPlugin createMenu() {
807         if (mMenuRow.getMenuView() == null) {
808             mMenuRow.createMenu(this);
809             mMenuRow.setAppName(mAppName);
810             FrameLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
811                     LayoutParams.MATCH_PARENT);
812             addView(mMenuRow.getMenuView(), MENU_VIEW_INDEX, lp);
813         }
814         return mMenuRow;
815     }
816 
817     public NotificationMenuRowPlugin getProvider() {
818         return mMenuRow;
819     }
820 
821     public void onDensityOrFontScaleChanged() {
822         initDimens();
823         // Let's update our childrencontainer. This is intentionally not guarded with
824         // mIsSummaryWithChildren since we might have had children but not anymore.
825         if (mChildrenContainer != null) {
826             mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification);
827         }
828         if (mGuts != null) {
829             View oldGuts = mGuts;
830             int index = indexOfChild(oldGuts);
831             removeView(oldGuts);
832             mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(
833                     R.layout.notification_guts, this, false);
834             mGuts.setVisibility(oldGuts.getVisibility());
835             addView(mGuts, index);
836         }
837         View oldMenu = mMenuRow.getMenuView();
838         if (oldMenu != null) {
839             int menuIndex = indexOfChild(oldMenu);
840             removeView(oldMenu);
841             mMenuRow.createMenu(ExpandableNotificationRow.this);
842             mMenuRow.setAppName(mAppName);
843             addView(mMenuRow.getMenuView(), menuIndex);
844         }
845         for (NotificationContentView l : mLayouts) {
846             l.reInflateViews();
847         }
848         mNotificationInflater.onDensityOrFontScaleChanged();
849         onNotificationUpdated();
850     }
851 
852     @Override
853     public void onConfigurationChanged(Configuration newConfig) {
854         if (mMenuRow.getMenuView() != null) {
855             mMenuRow.onConfigurationChanged();
856         }
857     }
858 
859     public void setContentBackground(int customBackgroundColor, boolean animate,
860             NotificationContentView notificationContentView) {
861         if (getShowingLayout() == notificationContentView) {
862             setTintColor(customBackgroundColor, animate);
863         }
864     }
865 
866     public void closeRemoteInput() {
867         for (NotificationContentView l : mLayouts) {
868             l.closeRemoteInput();
869         }
870     }
871 
872     /**
873      * Set by how much the single line view should be indented.
874      */
875     public void setSingleLineWidthIndention(int indention) {
876         mPrivateLayout.setSingleLineWidthIndention(indention);
877     }
878 
879     public int getNotificationColor() {
880         return mNotificationColor;
881     }
882 
883     private void updateNotificationColor() {
884         mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
885                 getStatusBarNotification().getNotification().color,
886                 getBackgroundColorWithoutTint());
887         mNotificationColorAmbient = NotificationColorUtil.resolveAmbientColor(mContext,
888                 getStatusBarNotification().getNotification().color);
889     }
890 
891     public HybridNotificationView getSingleLineView() {
892         return mPrivateLayout.getSingleLineView();
893     }
894 
895     public HybridNotificationView getAmbientSingleLineView() {
896         return getShowingLayout().getAmbientSingleLineChild();
897     }
898 
899     public boolean isOnKeyguard() {
900         return mOnKeyguard;
901     }
902 
903     public void removeAllChildren() {
904         List<ExpandableNotificationRow> notificationChildren
905                 = mChildrenContainer.getNotificationChildren();
906         ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
907         for (int i = 0; i < clonedList.size(); i++) {
908             ExpandableNotificationRow row = clonedList.get(i);
909             if (row.keepInParent()) {
910                 continue;
911             }
912             mChildrenContainer.removeNotification(row);
913             row.setIsChildInGroup(false, null);
914         }
915         onChildrenCountChanged();
916     }
917 
918     public void setForceUnlocked(boolean forceUnlocked) {
919         mForceUnlocked = forceUnlocked;
920         if (mIsSummaryWithChildren) {
921             List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
922             for (ExpandableNotificationRow child : notificationChildren) {
923                 child.setForceUnlocked(forceUnlocked);
924             }
925         }
926     }
927 
928     public void setDismissed(boolean dismissed, boolean fromAccessibility) {
929         mDismissed = dismissed;
930         mGroupParentWhenDismissed = mNotificationParent;
931         mRefocusOnDismiss = fromAccessibility;
932         mChildAfterViewWhenDismissed = null;
933         if (isChildInGroup()) {
934             List<ExpandableNotificationRow> notificationChildren =
935                     mNotificationParent.getNotificationChildren();
936             int i = notificationChildren.indexOf(this);
937             if (i != -1 && i < notificationChildren.size() - 1) {
938                 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
939             }
940         }
941     }
942 
943     public boolean isDismissed() {
944         return mDismissed;
945     }
946 
947     public boolean keepInParent() {
948         return mKeepInParent;
949     }
950 
951     public void setKeepInParent(boolean keepInParent) {
952         mKeepInParent = keepInParent;
953     }
954 
955     public boolean isRemoved() {
956         return mRemoved;
957     }
958 
959     public void setRemoved() {
960         mRemoved = true;
961         mTranslationWhenRemoved = getTranslationY();
962         mWasChildInGroupWhenRemoved = isChildInGroup();
963         if (isChildInGroup()) {
964             mTranslationWhenRemoved += getNotificationParent().getTranslationY();
965         }
966         mPrivateLayout.setRemoved();
967     }
968 
969     public boolean wasChildInGroupWhenRemoved() {
970         return mWasChildInGroupWhenRemoved;
971     }
972 
973     public float getTranslationWhenRemoved() {
974         return mTranslationWhenRemoved;
975     }
976 
977     public NotificationChildrenContainer getChildrenContainer() {
978         return mChildrenContainer;
979     }
980 
981     public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
982         mHeadsupDisappearRunning = headsUpAnimatingAway;
983         mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
984     }
985 
986     /**
987      * @return if the view was just heads upped and is now animating away. During such a time the
988      * layout needs to be kept consistent
989      */
990     public boolean isHeadsUpAnimatingAway() {
991         return mHeadsupDisappearRunning;
992     }
993 
994     public View getChildAfterViewWhenDismissed() {
995         return mChildAfterViewWhenDismissed;
996     }
997 
998     public View getGroupParentWhenDismissed() {
999         return mGroupParentWhenDismissed;
1000     }
1001 
1002     public void performDismiss() {
1003         if (mOnDismissRunnable != null) {
1004             mOnDismissRunnable.run();
1005         }
1006     }
1007 
1008     public void setOnDismissRunnable(Runnable onDismissRunnable) {
1009         mOnDismissRunnable = onDismissRunnable;
1010     }
1011 
1012     public View getNotificationIcon() {
1013         NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
1014         if (notificationHeader != null) {
1015             return notificationHeader.getIcon();
1016         }
1017         return null;
1018     }
1019 
1020     /**
1021      * @return whether the notification is currently showing a view with an icon.
1022      */
1023     public boolean isShowingIcon() {
1024         if (areGutsExposed()) {
1025             return false;
1026         }
1027         return getVisibleNotificationHeader() != null;
1028     }
1029 
1030     /**
1031      * Set how much this notification is transformed into an icon.
1032      *
1033      * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
1034      *                                 to the content away
1035      * @param isLastChild is this the last child in the list. If true, then the transformation is
1036      *                    different since it's content fades out.
1037      */
1038     public void setContentTransformationAmount(float contentTransformationAmount,
1039             boolean isLastChild) {
1040         boolean changeTransformation = isLastChild != mIsLastChild;
1041         changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
1042         mIsLastChild = isLastChild;
1043         mContentTransformationAmount = contentTransformationAmount;
1044         if (changeTransformation) {
1045             updateContentTransformation();
1046         }
1047     }
1048 
1049     /**
1050      * Set the icons to be visible of this notification.
1051      */
1052     public void setIconsVisible(boolean iconsVisible) {
1053         if (iconsVisible != mIconsVisible) {
1054             mIconsVisible = iconsVisible;
1055             updateIconVisibilities();
1056         }
1057     }
1058 
1059     @Override
1060     protected void onBelowSpeedBumpChanged() {
1061         updateIconVisibilities();
1062     }
1063 
1064     private void updateContentTransformation() {
1065         float contentAlpha;
1066         float translationY = -mContentTransformationAmount * mIconTransformContentShift;
1067         if (mIsLastChild) {
1068             contentAlpha = 1.0f - mContentTransformationAmount;
1069             contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
1070             contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
1071             translationY *= 0.4f;
1072         } else {
1073             contentAlpha = 1.0f;
1074         }
1075         for (NotificationContentView l : mLayouts) {
1076             l.setAlpha(contentAlpha);
1077             l.setTranslationY(translationY);
1078         }
1079         if (mChildrenContainer != null) {
1080             mChildrenContainer.setAlpha(contentAlpha);
1081             mChildrenContainer.setTranslationY(translationY);
1082             // TODO: handle children fade out better
1083         }
1084     }
1085 
1086     private void updateIconVisibilities() {
1087         boolean visible = isChildInGroup()
1088                 || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS)
1089                 || mIconsVisible;
1090         for (NotificationContentView l : mLayouts) {
1091             l.setIconsVisible(visible);
1092         }
1093         if (mChildrenContainer != null) {
1094             mChildrenContainer.setIconsVisible(visible);
1095         }
1096     }
1097 
1098     /**
1099      * Get the relative top padding of a view relative to this view. This recursively walks up the
1100      * hierarchy and does the corresponding measuring.
1101      *
1102      * @param view the view to the the padding for. The requested view has to be a child of this
1103      *             notification.
1104      * @return the toppadding
1105      */
1106     public int getRelativeTopPadding(View view) {
1107         int topPadding = 0;
1108         while (view.getParent() instanceof ViewGroup) {
1109             topPadding += view.getTop();
1110             view = (View) view.getParent();
1111             if (view instanceof ExpandableNotificationRow) {
1112                 return topPadding;
1113             }
1114         }
1115         return topPadding;
1116     }
1117 
1118     public float getContentTranslation() {
1119         return mPrivateLayout.getTranslationY();
1120     }
1121 
1122     public void setIsLowPriority(boolean isLowPriority) {
1123         mIsLowPriority = isLowPriority;
1124         mPrivateLayout.setIsLowPriority(isLowPriority);
1125         mNotificationInflater.setIsLowPriority(mIsLowPriority);
1126         if (mChildrenContainer != null) {
1127             mChildrenContainer.setIsLowPriority(isLowPriority);
1128         }
1129     }
1130 
1131 
1132     public void setLowPriorityStateUpdated(boolean lowPriorityStateUpdated) {
1133         mLowPriorityStateUpdated = lowPriorityStateUpdated;
1134     }
1135 
1136     public boolean hasLowPriorityStateUpdated() {
1137         return mLowPriorityStateUpdated;
1138     }
1139 
1140     public boolean isLowPriority() {
1141         return mIsLowPriority;
1142     }
1143 
1144     public void setUseIncreasedCollapsedHeight(boolean use) {
1145         mUseIncreasedCollapsedHeight = use;
1146         mNotificationInflater.setUsesIncreasedHeight(use);
1147     }
1148 
1149     public void setUseIncreasedHeadsUpHeight(boolean use) {
1150         mUseIncreasedHeadsUpHeight = use;
1151         mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
1152     }
1153 
1154     public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
1155         mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
1156     }
1157 
1158     public void setInflationCallback(InflationCallback callback) {
1159         mNotificationInflater.setInflationCallback(callback);
1160     }
1161 
1162     public void setNeedsRedaction(boolean needsRedaction) {
1163         mNotificationInflater.setRedactAmbient(needsRedaction);
1164     }
1165 
1166     @VisibleForTesting
1167     public NotificationInflater getNotificationInflater() {
1168         return mNotificationInflater;
1169     }
1170 
1171     public int getNotificationColorAmbient() {
1172         return mNotificationColorAmbient;
1173     }
1174 
1175     public interface ExpansionLogger {
1176         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
1177     }
1178 
1179     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
1180         super(context, attrs);
1181         mFalsingManager = FalsingManager.getInstance(context);
1182         mNotificationInflater = new NotificationInflater(this);
1183         mMenuRow = new NotificationMenuRow(mContext);
1184         initDimens();
1185     }
1186 
1187     private void initDimens() {
1188         mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
1189         mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
1190         mNotificationMinHeightLarge = getFontScaledHeight(
1191                 R.dimen.notification_min_height_increased);
1192         mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
1193         mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
1194         mMaxHeadsUpHeightLegacy = getFontScaledHeight(
1195                 R.dimen.notification_max_heads_up_height_legacy);
1196         mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
1197         mMaxHeadsUpHeightIncreased = getFontScaledHeight(
1198                 R.dimen.notification_max_heads_up_height_increased);
1199         mIncreasedPaddingBetweenElements = getResources()
1200                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
1201         mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize(
1202                 R.dimen.notification_icon_transform_content_shift);
1203     }
1204 
1205     /**
1206      * @param dimenId the dimen to look up
1207      * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
1208      */
1209     private int getFontScaledHeight(int dimenId) {
1210         int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
1211         float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
1212                 getResources().getDisplayMetrics().density);
1213         return (int) (dimensionPixelSize * factor);
1214     }
1215 
1216     /**
1217      * Resets this view so it can be re-used for an updated notification.
1218      */
1219     public void reset() {
1220         mShowingPublicInitialized = false;
1221         onHeightReset();
1222         requestLayout();
1223     }
1224 
1225     @Override
1226     protected void onFinishInflate() {
1227         super.onFinishInflate();
1228         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
1229         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
1230         mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
1231 
1232         for (NotificationContentView l : mLayouts) {
1233             l.setExpandClickListener(mExpandClickListener);
1234             l.setContainingNotification(this);
1235         }
1236         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
1237         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
1238             @Override
1239             public void onInflate(ViewStub stub, View inflated) {
1240                 mGuts = (NotificationGuts) inflated;
1241                 mGuts.setClipTopAmount(getClipTopAmount());
1242                 mGuts.setActualHeight(getActualHeight());
1243                 mGutsStub = null;
1244             }
1245         });
1246         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
1247         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
1248 
1249             @Override
1250             public void onInflate(ViewStub stub, View inflated) {
1251                 mChildrenContainer = (NotificationChildrenContainer) inflated;
1252                 mChildrenContainer.setIsLowPriority(mIsLowPriority);
1253                 mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
1254                 mChildrenContainer.onNotificationUpdated();
1255                 mTranslateableViews.add(mChildrenContainer);
1256             }
1257         });
1258 
1259         // Add the views that we translate to reveal the menu
1260         mTranslateableViews = new ArrayList<View>();
1261         for (int i = 0; i < getChildCount(); i++) {
1262             mTranslateableViews.add(getChildAt(i));
1263         }
1264         // Remove views that don't translate
1265         mTranslateableViews.remove(mChildrenContainerStub);
1266         mTranslateableViews.remove(mGutsStub);
1267     }
1268 
1269     public void resetTranslation() {
1270         if (mTranslateAnim != null) {
1271             mTranslateAnim.cancel();
1272         }
1273         if (mTranslateableViews != null) {
1274             for (int i = 0; i < mTranslateableViews.size(); i++) {
1275                 mTranslateableViews.get(i).setTranslationX(0);
1276             }
1277         }
1278         invalidateOutline();
1279         mMenuRow.resetMenu();
1280     }
1281 
1282     public void animateTranslateNotification(final float leftTarget) {
1283         if (mTranslateAnim != null) {
1284             mTranslateAnim.cancel();
1285         }
1286         mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
1287         if (mTranslateAnim != null) {
1288             mTranslateAnim.start();
1289         }
1290     }
1291 
1292     @Override
1293     public void setTranslation(float translationX) {
1294         if (areGutsExposed()) {
1295             // Don't translate if guts are showing.
1296             return;
1297         }
1298         // Translate the group of views
1299         for (int i = 0; i < mTranslateableViews.size(); i++) {
1300             if (mTranslateableViews.get(i) != null) {
1301                 mTranslateableViews.get(i).setTranslationX(translationX);
1302             }
1303         }
1304         invalidateOutline();
1305         if (mMenuRow.getMenuView() != null) {
1306             mMenuRow.onTranslationUpdate(translationX);
1307         }
1308     }
1309 
1310     @Override
1311     public float getTranslation() {
1312         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
1313             // All of the views in the list should have same translation, just use first one.
1314             return mTranslateableViews.get(0).getTranslationX();
1315         }
1316         return 0;
1317     }
1318 
1319     public Animator getTranslateViewAnimator(final float leftTarget,
1320             AnimatorUpdateListener listener) {
1321         if (mTranslateAnim != null) {
1322             mTranslateAnim.cancel();
1323         }
1324         if (areGutsExposed()) {
1325             // No translation if guts are exposed.
1326             return null;
1327         }
1328         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
1329                 leftTarget);
1330         if (listener != null) {
1331             translateAnim.addUpdateListener(listener);
1332         }
1333         translateAnim.addListener(new AnimatorListenerAdapter() {
1334             boolean cancelled = false;
1335 
1336             @Override
1337             public void onAnimationCancel(Animator anim) {
1338                 cancelled = true;
1339             }
1340 
1341             @Override
1342             public void onAnimationEnd(Animator anim) {
1343                 if (!cancelled && leftTarget == 0) {
1344                     mMenuRow.resetMenu();
1345                     mTranslateAnim = null;
1346                 }
1347             }
1348         });
1349         mTranslateAnim = translateAnim;
1350         return translateAnim;
1351     }
1352 
1353     public void inflateGuts() {
1354         if (mGuts == null) {
1355             mGutsStub.inflate();
1356         }
1357     }
1358 
1359     private void updateChildrenVisibility() {
1360         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
1361                 : INVISIBLE);
1362         if (mChildrenContainer != null) {
1363             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
1364                     : INVISIBLE);
1365         }
1366         // The limits might have changed if the view suddenly became a group or vice versa
1367         updateLimits();
1368     }
1369 
1370     @Override
1371     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
1372         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
1373             // Add a record for the entire layout since its content is somehow small.
1374             // The event comes from a leaf view that is interacted with.
1375             AccessibilityEvent record = AccessibilityEvent.obtain();
1376             onInitializeAccessibilityEvent(record);
1377             dispatchPopulateAccessibilityEvent(record);
1378             event.appendRecord(record);
1379             return true;
1380         }
1381         return false;
1382     }
1383 
1384     @Override
1385     public void setDark(boolean dark, boolean fade, long delay) {
1386         super.setDark(dark, fade, delay);
1387         if (!mIsHeadsUp) {
1388             // Only fade the showing view of the pulsing notification.
1389             fade = false;
1390         }
1391         final NotificationContentView showing = getShowingLayout();
1392         if (showing != null) {
1393             showing.setDark(dark, fade, delay);
1394         }
1395         if (mIsSummaryWithChildren) {
1396             mChildrenContainer.setDark(dark, fade, delay);
1397         }
1398         updateShelfIconColor();
1399     }
1400 
1401     public boolean isExpandable() {
1402         if (mIsSummaryWithChildren && !mShowingPublic) {
1403             return !mChildrenExpanded;
1404         }
1405         return mExpandable;
1406     }
1407 
1408     public void setExpandable(boolean expandable) {
1409         mExpandable = expandable;
1410         mPrivateLayout.updateExpandButtons(isExpandable());
1411     }
1412 
1413     @Override
1414     public void setClipToActualHeight(boolean clipToActualHeight) {
1415         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
1416         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
1417     }
1418 
1419     /**
1420      * @return whether the user has changed the expansion state
1421      */
1422     public boolean hasUserChangedExpansion() {
1423         return mHasUserChangedExpansion;
1424     }
1425 
1426     public boolean isUserExpanded() {
1427         return mUserExpanded;
1428     }
1429 
1430     /**
1431      * Set this notification to be expanded by the user
1432      *
1433      * @param userExpanded whether the user wants this notification to be expanded
1434      */
1435     public void setUserExpanded(boolean userExpanded) {
1436         setUserExpanded(userExpanded, false /* allowChildExpansion */);
1437     }
1438 
1439     /**
1440      * Set this notification to be expanded by the user
1441      *
1442      * @param userExpanded whether the user wants this notification to be expanded
1443      * @param allowChildExpansion whether a call to this method allows expanding children
1444      */
1445     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
1446         mFalsingManager.setNotificationExpanded();
1447         if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion
1448                 && !mChildrenContainer.showingAsLowPriority()) {
1449             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
1450             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
1451             onExpansionChanged(true /* userAction */, wasExpanded);
1452             return;
1453         }
1454         if (userExpanded && !mExpandable) return;
1455         final boolean wasExpanded = isExpanded();
1456         mHasUserChangedExpansion = true;
1457         mUserExpanded = userExpanded;
1458         onExpansionChanged(true /* userAction */, wasExpanded);
1459     }
1460 
1461     public void resetUserExpansion() {
1462         boolean changed = mUserExpanded;
1463         mHasUserChangedExpansion = false;
1464         mUserExpanded = false;
1465         if (changed && mIsSummaryWithChildren) {
1466             mChildrenContainer.onExpansionChanged();
1467         }
1468         updateShelfIconColor();
1469     }
1470 
1471     public boolean isUserLocked() {
1472         return mUserLocked && !mForceUnlocked;
1473     }
1474 
1475     public void setUserLocked(boolean userLocked) {
1476         mUserLocked = userLocked;
1477         mPrivateLayout.setUserExpanding(userLocked);
1478         // This is intentionally not guarded with mIsSummaryWithChildren since we might have had
1479         // children but not anymore.
1480         if (mChildrenContainer != null) {
1481             mChildrenContainer.setUserLocked(userLocked);
1482             if (mIsSummaryWithChildren && (userLocked || !isGroupExpanded())) {
1483                 updateBackgroundForGroupState();
1484             }
1485         }
1486     }
1487 
1488     /**
1489      * @return has the system set this notification to be expanded
1490      */
1491     public boolean isSystemExpanded() {
1492         return mIsSystemExpanded;
1493     }
1494 
1495     /**
1496      * Set this notification to be expanded by the system.
1497      *
1498      * @param expand whether the system wants this notification to be expanded.
1499      */
1500     public void setSystemExpanded(boolean expand) {
1501         if (expand != mIsSystemExpanded) {
1502             final boolean wasExpanded = isExpanded();
1503             mIsSystemExpanded = expand;
1504             notifyHeightChanged(false /* needsAnimation */);
1505             onExpansionChanged(false /* userAction */, wasExpanded);
1506             if (mIsSummaryWithChildren) {
1507                 mChildrenContainer.updateGroupOverflow();
1508             }
1509         }
1510     }
1511 
1512     /**
1513      * @param onKeyguard whether to prevent notification expansion
1514      */
1515     public void setOnKeyguard(boolean onKeyguard) {
1516         if (onKeyguard != mOnKeyguard) {
1517             final boolean wasExpanded = isExpanded();
1518             mOnKeyguard = onKeyguard;
1519             onExpansionChanged(false /* userAction */, wasExpanded);
1520             if (wasExpanded != isExpanded()) {
1521                 if (mIsSummaryWithChildren) {
1522                     mChildrenContainer.updateGroupOverflow();
1523                 }
1524                 notifyHeightChanged(false /* needsAnimation */);
1525             }
1526         }
1527     }
1528 
1529     /**
1530      * @return Can the underlying notification be cleared? This can be different from whether the
1531      *         notification can be dismissed in case notifications are sensitive on the lockscreen.
1532      * @see #canViewBeDismissed()
1533      */
1534     public boolean isClearable() {
1535         if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) {
1536             return false;
1537         }
1538         if (mIsSummaryWithChildren) {
1539             List<ExpandableNotificationRow> notificationChildren =
1540                     mChildrenContainer.getNotificationChildren();
1541             for (int i = 0; i < notificationChildren.size(); i++) {
1542                 ExpandableNotificationRow child = notificationChildren.get(i);
1543                 if (!child.isClearable()) {
1544                     return false;
1545                 }
1546             }
1547         }
1548         return true;
1549     }
1550 
1551     @Override
1552     public int getIntrinsicHeight() {
1553         if (isUserLocked()) {
1554             return getActualHeight();
1555         }
1556         if (mGuts != null && mGuts.isExposed()) {
1557             return mGuts.getIntrinsicHeight();
1558         } else if ((isChildInGroup() && !isGroupExpanded())) {
1559             return mPrivateLayout.getMinHeight();
1560         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
1561             return getMinHeight();
1562         } else if (mIsSummaryWithChildren && (!mOnKeyguard || mShowAmbient)) {
1563             return mChildrenContainer.getIntrinsicHeight();
1564         } else if (isHeadsUpAllowed() && (mIsHeadsUp || mHeadsupDisappearRunning)) {
1565             if (isPinned() || mHeadsupDisappearRunning) {
1566                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
1567             } else if (isExpanded()) {
1568                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
1569             } else {
1570                 return Math.max(getCollapsedHeight(), mHeadsUpHeight);
1571             }
1572         } else if (isExpanded()) {
1573             return getMaxExpandHeight();
1574         } else {
1575             return getCollapsedHeight();
1576         }
1577     }
1578 
1579     private boolean isHeadsUpAllowed() {
1580         return !mOnKeyguard && !mShowAmbient;
1581     }
1582 
1583     @Override
1584     public boolean isGroupExpanded() {
1585         return mGroupManager.isGroupExpanded(mStatusBarNotification);
1586     }
1587 
1588     private void onChildrenCountChanged() {
1589         mIsSummaryWithChildren = StatusBar.ENABLE_CHILD_NOTIFICATIONS
1590                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
1591         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
1592             mChildrenContainer.recreateNotificationHeader(mExpandClickListener
1593             );
1594         }
1595         getShowingLayout().updateBackgroundColor(false /* animate */);
1596         mPrivateLayout.updateExpandButtons(isExpandable());
1597         updateChildrenHeaderAppearance();
1598         updateChildrenVisibility();
1599     }
1600 
1601     public void updateChildrenHeaderAppearance() {
1602         if (mIsSummaryWithChildren) {
1603             mChildrenContainer.updateChildrenHeaderAppearance();
1604         }
1605     }
1606 
1607     /**
1608      * Check whether the view state is currently expanded. This is given by the system in {@link
1609      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
1610      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
1611      * view can differ from this state, if layout params are modified from outside.
1612      *
1613      * @return whether the view state is currently expanded.
1614      */
1615     public boolean isExpanded() {
1616         return isExpanded(false /* allowOnKeyguard */);
1617     }
1618 
1619     public boolean isExpanded(boolean allowOnKeyguard) {
1620         return (!mOnKeyguard || allowOnKeyguard)
1621                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
1622                 || isUserExpanded());
1623     }
1624 
1625     private boolean isSystemChildExpanded() {
1626         return mIsSystemChildExpanded;
1627     }
1628 
1629     public void setSystemChildExpanded(boolean expanded) {
1630         mIsSystemChildExpanded = expanded;
1631     }
1632 
1633     public void setLayoutListener(LayoutListener listener) {
1634         mLayoutListener = listener;
1635     }
1636 
1637     public void removeListener() {
1638         mLayoutListener = null;
1639     }
1640 
1641     @Override
1642     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1643         super.onLayout(changed, left, top, right, bottom);
1644         updateMaxHeights();
1645         if (mMenuRow.getMenuView() != null) {
1646             mMenuRow.onHeightUpdate();
1647         }
1648         updateContentShiftHeight();
1649         if (mLayoutListener != null) {
1650             mLayoutListener.onLayout();
1651         }
1652     }
1653 
1654     /**
1655      * Updates the content shift height such that the header is completely hidden when coming from
1656      * the top.
1657      */
1658     private void updateContentShiftHeight() {
1659         NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
1660         if (notificationHeader != null) {
1661             CachingIconView icon = notificationHeader.getIcon();
1662             mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
1663         } else {
1664             mIconTransformContentShift = mIconTransformContentShiftNoIcon;
1665         }
1666     }
1667 
1668     private void updateMaxHeights() {
1669         int intrinsicBefore = getIntrinsicHeight();
1670         View expandedChild = mPrivateLayout.getExpandedChild();
1671         if (expandedChild == null) {
1672             expandedChild = mPrivateLayout.getContractedChild();
1673         }
1674         mMaxExpandHeight = expandedChild.getHeight();
1675         View headsUpChild = mPrivateLayout.getHeadsUpChild();
1676         if (headsUpChild == null) {
1677             headsUpChild = mPrivateLayout.getContractedChild();
1678         }
1679         mHeadsUpHeight = headsUpChild.getHeight();
1680         if (intrinsicBefore != getIntrinsicHeight()) {
1681             notifyHeightChanged(true  /* needsAnimation */);
1682         }
1683     }
1684 
1685     @Override
1686     public void notifyHeightChanged(boolean needsAnimation) {
1687         super.notifyHeightChanged(needsAnimation);
1688         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
1689     }
1690 
1691     public void setSensitive(boolean sensitive, boolean hideSensitive) {
1692         mSensitive = sensitive;
1693         mSensitiveHiddenInGeneral = hideSensitive;
1694     }
1695 
1696     @Override
1697     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
1698         mHideSensitiveForIntrinsicHeight = hideSensitive;
1699         if (mIsSummaryWithChildren) {
1700             List<ExpandableNotificationRow> notificationChildren =
1701                     mChildrenContainer.getNotificationChildren();
1702             for (int i = 0; i < notificationChildren.size(); i++) {
1703                 ExpandableNotificationRow child = notificationChildren.get(i);
1704                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
1705             }
1706         }
1707     }
1708 
1709     @Override
1710     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
1711             long duration) {
1712         boolean oldShowingPublic = mShowingPublic;
1713         mShowingPublic = mSensitive && hideSensitive;
1714         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
1715             return;
1716         }
1717 
1718         // bail out if no public version
1719         if (mPublicLayout.getChildCount() == 0) return;
1720 
1721         if (!animated) {
1722             mPublicLayout.animate().cancel();
1723             mPrivateLayout.animate().cancel();
1724             if (mChildrenContainer != null) {
1725                 mChildrenContainer.animate().cancel();
1726                 mChildrenContainer.setAlpha(1f);
1727             }
1728             mPublicLayout.setAlpha(1f);
1729             mPrivateLayout.setAlpha(1f);
1730             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
1731             updateChildrenVisibility();
1732         } else {
1733             animateShowingPublic(delay, duration);
1734         }
1735         NotificationContentView showingLayout = getShowingLayout();
1736         showingLayout.updateBackgroundColor(animated);
1737         mPrivateLayout.updateExpandButtons(isExpandable());
1738         updateShelfIconColor();
1739         showingLayout.setDark(isDark(), false /* animate */, 0 /* delay */);
1740         mShowingPublicInitialized = true;
1741     }
1742 
1743     private void animateShowingPublic(long delay, long duration) {
1744         View[] privateViews = mIsSummaryWithChildren
1745                 ? new View[] {mChildrenContainer}
1746                 : new View[] {mPrivateLayout};
1747         View[] publicViews = new View[] {mPublicLayout};
1748         View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
1749         View[] shownChildren = mShowingPublic ? publicViews : privateViews;
1750         for (final View hiddenView : hiddenChildren) {
1751             hiddenView.setVisibility(View.VISIBLE);
1752             hiddenView.animate().cancel();
1753             hiddenView.animate()
1754                     .alpha(0f)
1755                     .setStartDelay(delay)
1756                     .setDuration(duration)
1757                     .withEndAction(new Runnable() {
1758                         @Override
1759                         public void run() {
1760                             hiddenView.setVisibility(View.INVISIBLE);
1761                         }
1762                     });
1763         }
1764         for (View showView : shownChildren) {
1765             showView.setVisibility(View.VISIBLE);
1766             showView.setAlpha(0f);
1767             showView.animate().cancel();
1768             showView.animate()
1769                     .alpha(1f)
1770                     .setStartDelay(delay)
1771                     .setDuration(duration);
1772         }
1773     }
1774 
1775     @Override
1776     public boolean mustStayOnScreen() {
1777         return mIsHeadsUp;
1778     }
1779 
1780     /**
1781      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
1782      *         otherwise some state might not be updated. To request about the general clearability
1783      *         see {@link #isClearable()}.
1784      */
1785     public boolean canViewBeDismissed() {
1786         return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
1787     }
1788 
1789     public void makeActionsVisibile() {
1790         setUserExpanded(true, true);
1791         if (isChildInGroup()) {
1792             mGroupManager.setGroupExpanded(mStatusBarNotification, true);
1793         }
1794         notifyHeightChanged(false /* needsAnimation */);
1795     }
1796 
1797     public void setChildrenExpanded(boolean expanded, boolean animate) {
1798         mChildrenExpanded = expanded;
1799         if (mChildrenContainer != null) {
1800             mChildrenContainer.setChildrenExpanded(expanded);
1801         }
1802         updateBackgroundForGroupState();
1803         updateClickAndFocus();
1804     }
1805 
1806     public static void applyTint(View v, int color) {
1807         int alpha;
1808         if (color != 0) {
1809             alpha = COLORED_DIVIDER_ALPHA;
1810         } else {
1811             color = 0xff000000;
1812             alpha = DEFAULT_DIVIDER_ALPHA;
1813         }
1814         if (v.getBackground() instanceof ColorDrawable) {
1815             ColorDrawable background = (ColorDrawable) v.getBackground();
1816             background.mutate();
1817             background.setColor(color);
1818             background.setAlpha(alpha);
1819         }
1820     }
1821 
1822     public int getMaxExpandHeight() {
1823         return mMaxExpandHeight;
1824     }
1825 
1826     public boolean areGutsExposed() {
1827         return (mGuts != null && mGuts.isExposed());
1828     }
1829 
1830     @Override
1831     public boolean isContentExpandable() {
1832         if (mIsSummaryWithChildren && !mShowingPublic) {
1833             return true;
1834         }
1835         NotificationContentView showingLayout = getShowingLayout();
1836         return showingLayout.isContentExpandable();
1837     }
1838 
1839     @Override
1840     protected View getContentView() {
1841         if (mIsSummaryWithChildren && !mShowingPublic) {
1842             return mChildrenContainer;
1843         }
1844         return getShowingLayout();
1845     }
1846 
1847     @Override
1848     protected void onAppearAnimationFinished(boolean wasAppearing) {
1849         super.onAppearAnimationFinished(wasAppearing);
1850         if (wasAppearing) {
1851             // During the animation the visible view might have changed, so let's make sure all
1852             // alphas are reset
1853             if (mChildrenContainer != null) {
1854                 mChildrenContainer.setAlpha(1.0f);
1855                 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
1856             }
1857             for (NotificationContentView l : mLayouts) {
1858                 l.setAlpha(1.0f);
1859                 l.setLayerType(LAYER_TYPE_NONE, null);
1860             }
1861         }
1862     }
1863 
1864     @Override
1865     public int getExtraBottomPadding() {
1866         if (mIsSummaryWithChildren && isGroupExpanded()) {
1867             return mIncreasedPaddingBetweenElements;
1868         }
1869         return 0;
1870     }
1871 
1872     @Override
1873     public void setActualHeight(int height, boolean notifyListeners) {
1874         boolean changed = height != getActualHeight();
1875         super.setActualHeight(height, notifyListeners);
1876         if (changed && isRemoved()) {
1877             // TODO: remove this once we found the gfx bug for this.
1878             // This is a hack since a removed view sometimes would just stay blank. it occured
1879             // when sending yourself a message and then clicking on it.
1880             ViewGroup parent = (ViewGroup) getParent();
1881             if (parent != null) {
1882                 parent.invalidate();
1883             }
1884         }
1885         if (mGuts != null && mGuts.isExposed()) {
1886             mGuts.setActualHeight(height);
1887             return;
1888         }
1889         int contentHeight = Math.max(getMinHeight(), height);
1890         for (NotificationContentView l : mLayouts) {
1891             l.setContentHeight(contentHeight);
1892         }
1893         if (mIsSummaryWithChildren) {
1894             mChildrenContainer.setActualHeight(height);
1895         }
1896         if (mGuts != null) {
1897             mGuts.setActualHeight(height);
1898         }
1899     }
1900 
1901     @Override
1902     public int getMaxContentHeight() {
1903         if (mIsSummaryWithChildren && !mShowingPublic) {
1904             return mChildrenContainer.getMaxContentHeight();
1905         }
1906         NotificationContentView showingLayout = getShowingLayout();
1907         return showingLayout.getMaxHeight();
1908     }
1909 
1910     @Override
1911     public int getMinHeight() {
1912         if (mGuts != null && mGuts.isExposed()) {
1913             return mGuts.getIntrinsicHeight();
1914         } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
1915                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
1916         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
1917             return mChildrenContainer.getMinHeight();
1918         } else if (isHeadsUpAllowed() && mIsHeadsUp) {
1919             return mHeadsUpHeight;
1920         }
1921         NotificationContentView showingLayout = getShowingLayout();
1922         return showingLayout.getMinHeight();
1923     }
1924 
1925     @Override
1926     public int getCollapsedHeight() {
1927         if (mIsSummaryWithChildren && !mShowingPublic) {
1928             return mChildrenContainer.getCollapsedHeight();
1929         }
1930         return getMinHeight();
1931     }
1932 
1933     @Override
1934     public void setClipTopAmount(int clipTopAmount) {
1935         super.setClipTopAmount(clipTopAmount);
1936         for (NotificationContentView l : mLayouts) {
1937             l.setClipTopAmount(clipTopAmount);
1938         }
1939         if (mGuts != null) {
1940             mGuts.setClipTopAmount(clipTopAmount);
1941         }
1942     }
1943 
1944     @Override
1945     public void setClipBottomAmount(int clipBottomAmount) {
1946         if (clipBottomAmount != mClipBottomAmount) {
1947             super.setClipBottomAmount(clipBottomAmount);
1948             for (NotificationContentView l : mLayouts) {
1949                 l.setClipBottomAmount(clipBottomAmount);
1950             }
1951             if (mGuts != null) {
1952                 mGuts.setClipBottomAmount(clipBottomAmount);
1953             }
1954         }
1955         if (mChildrenContainer != null) {
1956             // We have to update this even if it hasn't changed, since the children locations can
1957             // have changed
1958             mChildrenContainer.setClipBottomAmount(clipBottomAmount);
1959         }
1960     }
1961 
1962     public boolean isMaxExpandHeightInitialized() {
1963         return mMaxExpandHeight != 0;
1964     }
1965 
1966     public NotificationContentView getShowingLayout() {
1967         return mShowingPublic ? mPublicLayout : mPrivateLayout;
1968     }
1969 
1970     public void setLegacy(boolean legacy) {
1971         for (NotificationContentView l : mLayouts) {
1972             l.setLegacy(legacy);
1973         }
1974     }
1975 
1976     @Override
1977     protected void updateBackgroundTint() {
1978         super.updateBackgroundTint();
1979         updateBackgroundForGroupState();
1980         if (mIsSummaryWithChildren) {
1981             List<ExpandableNotificationRow> notificationChildren =
1982                     mChildrenContainer.getNotificationChildren();
1983             for (int i = 0; i < notificationChildren.size(); i++) {
1984                 ExpandableNotificationRow child = notificationChildren.get(i);
1985                 child.updateBackgroundForGroupState();
1986             }
1987         }
1988     }
1989 
1990     /**
1991      * Called when a group has finished animating from collapsed or expanded state.
1992      */
1993     public void onFinishedExpansionChange() {
1994         mGroupExpansionChanging = false;
1995         updateBackgroundForGroupState();
1996     }
1997 
1998     /**
1999      * Updates the parent and children backgrounds in a group based on the expansion state.
2000      */
2001     public void updateBackgroundForGroupState() {
2002         if (mIsSummaryWithChildren) {
2003             // Only when the group has finished expanding do we hide its background.
2004             mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked();
2005             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
2006             List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
2007             for (int i = 0; i < children.size(); i++) {
2008                 children.get(i).updateBackgroundForGroupState();
2009             }
2010         } else if (isChildInGroup()) {
2011             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
2012             // Only show a background if the group is expanded OR if it is expanding / collapsing
2013             // and has a custom background color
2014             final boolean showBackground = isGroupExpanded()
2015                     || ((mNotificationParent.isGroupExpansionChanging()
2016                             || mNotificationParent.isUserLocked()) && childColor != 0);
2017             mShowNoBackground = !showBackground;
2018         } else {
2019             // Only children or parents ever need no background.
2020             mShowNoBackground = false;
2021         }
2022         updateOutline();
2023         updateBackground();
2024     }
2025 
2026     public int getPositionOfChild(ExpandableNotificationRow childRow) {
2027         if (mIsSummaryWithChildren) {
2028             return mChildrenContainer.getPositionInLinearLayout(childRow);
2029         }
2030         return 0;
2031     }
2032 
2033     public void setExpansionLogger(ExpansionLogger logger, String key) {
2034         mLogger = logger;
2035         mLoggingKey = key;
2036     }
2037 
2038     public void onExpandedByGesture(boolean userExpanded) {
2039         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
2040         if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) {
2041             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
2042         }
2043         MetricsLogger.action(mContext, event, userExpanded);
2044     }
2045 
2046     @Override
2047     public float getIncreasedPaddingAmount() {
2048         if (mIsSummaryWithChildren) {
2049             if (isGroupExpanded()) {
2050                 return 1.0f;
2051             } else if (isUserLocked()) {
2052                 return mChildrenContainer.getIncreasedPaddingAmount();
2053             }
2054         } else if (isColorized() && (!mIsLowPriority || isExpanded())) {
2055             return -1.0f;
2056         }
2057         return 0.0f;
2058     }
2059 
2060     private boolean isColorized() {
2061         return mIsColorized && mBgTint != NO_COLOR;
2062     }
2063 
2064     @Override
2065     protected boolean disallowSingleClick(MotionEvent event) {
2066         float x = event.getX();
2067         float y = event.getY();
2068         NotificationHeaderView header = getVisibleNotificationHeader();
2069         if (header != null) {
2070             return header.isInTouchRect(x - getTranslation(), y);
2071         }
2072         return super.disallowSingleClick(event);
2073     }
2074 
2075     private void onExpansionChanged(boolean userAction, boolean wasExpanded) {
2076         boolean nowExpanded = isExpanded();
2077         if (mIsSummaryWithChildren && (!mIsLowPriority || wasExpanded)) {
2078             nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
2079         }
2080         if (nowExpanded != wasExpanded) {
2081             updateShelfIconColor();
2082             if (mLogger != null) {
2083                 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded);
2084             }
2085             if (mIsSummaryWithChildren) {
2086                 mChildrenContainer.onExpansionChanged();
2087             }
2088         }
2089     }
2090 
2091     @Override
2092     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
2093         super.onInitializeAccessibilityNodeInfoInternal(info);
2094         if (canViewBeDismissed()) {
2095             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
2096         }
2097         boolean expandable = mShowingPublic;
2098         boolean isExpanded = false;
2099         if (!expandable) {
2100             if (mIsSummaryWithChildren) {
2101                 expandable = true;
2102                 if (!mIsLowPriority || isExpanded()) {
2103                     isExpanded = isGroupExpanded();
2104                 }
2105             } else {
2106                 expandable = mPrivateLayout.isContentExpandable();
2107                 isExpanded = isExpanded();
2108             }
2109         }
2110         if (expandable) {
2111             if (isExpanded) {
2112                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE);
2113             } else {
2114                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
2115             }
2116         }
2117     }
2118 
2119     @Override
2120     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
2121         if (super.performAccessibilityActionInternal(action, arguments)) {
2122             return true;
2123         }
2124         switch (action) {
2125             case AccessibilityNodeInfo.ACTION_DISMISS:
2126                 NotificationStackScrollLayout.performDismiss(this, mGroupManager,
2127                         true /* fromAccessibility */);
2128                 return true;
2129             case AccessibilityNodeInfo.ACTION_COLLAPSE:
2130             case AccessibilityNodeInfo.ACTION_EXPAND:
2131                 mExpandClickListener.onClick(this);
2132                 return true;
2133         }
2134         return false;
2135     }
2136 
2137     public boolean shouldRefocusOnDismiss() {
2138         return mRefocusOnDismiss || isAccessibilityFocused();
2139     }
2140 
2141     public interface OnExpandClickListener {
2142         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
2143     }
2144 
2145     @Override
2146     public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
2147         return new NotificationViewState(stackScrollState);
2148     }
2149 
2150     @Override
2151     public boolean isAboveShelf() {
2152         return !isOnKeyguard()
2153                 && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
2154     }
2155 
2156     public void setShowAmbient(boolean showAmbient) {
2157         if (showAmbient != mShowAmbient) {
2158             mShowAmbient = showAmbient;
2159             if (mChildrenContainer != null) {
2160                 mChildrenContainer.notifyShowAmbientChanged();
2161             }
2162             notifyHeightChanged(false /* needsAnimation */);
2163         }
2164     }
2165 
2166     public boolean isShowingAmbient() {
2167         return mShowAmbient;
2168     }
2169 
2170     public void setAboveShelf(boolean aboveShelf) {
2171         mAboveShelf = aboveShelf;
2172     }
2173 
2174     public static class NotificationViewState extends ExpandableViewState {
2175 
2176         private final StackScrollState mOverallState;
2177 
2178 
2179         private NotificationViewState(StackScrollState stackScrollState) {
2180             mOverallState = stackScrollState;
2181         }
2182 
2183         @Override
2184         public void applyToView(View view) {
2185             super.applyToView(view);
2186             if (view instanceof ExpandableNotificationRow) {
2187                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2188                 row.applyChildrenState(mOverallState);
2189             }
2190         }
2191 
2192         @Override
2193         protected void onYTranslationAnimationFinished(View view) {
2194             super.onYTranslationAnimationFinished(view);
2195             if (view instanceof ExpandableNotificationRow) {
2196                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
2197                 if (row.isHeadsUpAnimatingAway()) {
2198                     row.setHeadsUpAnimatingAway(false);
2199                 }
2200             }
2201         }
2202 
2203         @Override
2204         public void animateTo(View child, AnimationProperties properties) {
2205             super.animateTo(child, properties);
2206             if (child instanceof ExpandableNotificationRow) {
2207                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2208                 row.startChildAnimation(mOverallState, properties);
2209             }
2210         }
2211     }
2212 
2213     @VisibleForTesting
2214     protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
2215         mChildrenContainer = childrenContainer;
2216     }
2217 }
2218