• 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.ObjectAnimator;
22 import android.animation.ValueAnimator.AnimatorUpdateListener;
23 import android.annotation.Nullable;
24 import android.content.Context;
25 import android.graphics.drawable.AnimatedVectorDrawable;
26 import android.graphics.drawable.AnimationDrawable;
27 import android.graphics.drawable.ColorDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.service.notification.StatusBarNotification;
32 import android.util.AttributeSet;
33 import android.util.FloatProperty;
34 import android.util.Property;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.NotificationHeaderView;
38 import android.view.View;
39 import android.view.ViewStub;
40 import android.view.accessibility.AccessibilityEvent;
41 import android.view.accessibility.AccessibilityNodeInfo;
42 import android.widget.Chronometer;
43 import android.widget.ImageView;
44 
45 import com.android.internal.logging.MetricsLogger;
46 import com.android.internal.logging.MetricsProto.MetricsEvent;
47 import com.android.internal.util.NotificationColorUtil;
48 import com.android.systemui.R;
49 import com.android.systemui.classifier.FalsingManager;
50 import com.android.systemui.statusbar.notification.HybridNotificationView;
51 import com.android.systemui.statusbar.phone.NotificationGroupManager;
52 import com.android.systemui.statusbar.policy.HeadsUpManager;
53 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
54 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
55 import com.android.systemui.statusbar.stack.StackScrollState;
56 import com.android.systemui.statusbar.stack.StackStateAnimator;
57 import com.android.systemui.statusbar.stack.StackViewState;
58 
59 import java.util.ArrayList;
60 import java.util.List;
61 
62 public class ExpandableNotificationRow extends ActivatableNotificationView {
63 
64     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
65     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
66     private int mNotificationMinHeightLegacy;
67     private int mMaxHeadsUpHeightLegacy;
68     private int mMaxHeadsUpHeight;
69     private int mNotificationMinHeight;
70     private int mNotificationMaxHeight;
71     private int mIncreasedPaddingBetweenElements;
72 
73     /** Does this row contain layouts that can adapt to row expansion */
74     private boolean mExpandable;
75     /** Has the user actively changed the expansion state of this row */
76     private boolean mHasUserChangedExpansion;
77     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
78     private boolean mUserExpanded;
79 
80     /**
81      * Has this notification been expanded while it was pinned
82      */
83     private boolean mExpandedWhenPinned;
84     /** Is the user touching this row */
85     private boolean mUserLocked;
86     /** Are we showing the "public" version */
87     private boolean mShowingPublic;
88     private boolean mSensitive;
89     private boolean mSensitiveHiddenInGeneral;
90     private boolean mShowingPublicInitialized;
91     private boolean mHideSensitiveForIntrinsicHeight;
92 
93     /**
94      * Is this notification expanded by the system. The expansion state can be overridden by the
95      * user expansion.
96      */
97     private boolean mIsSystemExpanded;
98 
99     /**
100      * Whether the notification is on the keyguard and the expansion is disabled.
101      */
102     private boolean mOnKeyguard;
103 
104     private Animator mTranslateAnim;
105     private ArrayList<View> mTranslateableViews;
106     private NotificationContentView mPublicLayout;
107     private NotificationContentView mPrivateLayout;
108     private int mMaxExpandHeight;
109     private int mHeadsUpHeight;
110     private View mVetoButton;
111     private int mNotificationColor;
112     private ExpansionLogger mLogger;
113     private String mLoggingKey;
114     private NotificationSettingsIconRow mSettingsIconRow;
115     private NotificationGuts mGuts;
116     private NotificationData.Entry mEntry;
117     private StatusBarNotification mStatusBarNotification;
118     private String mAppName;
119     private boolean mIsHeadsUp;
120     private boolean mLastChronometerRunning = true;
121     private ViewStub mChildrenContainerStub;
122     private NotificationGroupManager mGroupManager;
123     private boolean mChildrenExpanded;
124     private boolean mIsSummaryWithChildren;
125     private NotificationChildrenContainer mChildrenContainer;
126     private ViewStub mSettingsIconRowStub;
127     private ViewStub mGutsStub;
128     private boolean mIsSystemChildExpanded;
129     private boolean mIsPinned;
130     private FalsingManager mFalsingManager;
131     private HeadsUpManager mHeadsUpManager;
132 
133     private boolean mJustClicked;
134     private boolean mIconAnimationRunning;
135     private boolean mShowNoBackground;
136     private ExpandableNotificationRow mNotificationParent;
137     private OnExpandClickListener mOnExpandClickListener;
138     private boolean mGroupExpansionChanging;
139 
140     private OnClickListener mExpandClickListener = new OnClickListener() {
141         @Override
142         public void onClick(View v) {
143             if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) {
144                 mGroupExpansionChanging = true;
145                 final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
146                 boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification);
147                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
148                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
149                         nowExpanded);
150                 logExpansionEvent(true /* userAction */, wasExpanded);
151             } else {
152                 if (v.isAccessibilityFocused()) {
153                     mPrivateLayout.setFocusOnVisibilityChange();
154                 }
155                 boolean nowExpanded;
156                 if (isPinned()) {
157                     nowExpanded = !mExpandedWhenPinned;
158                     mExpandedWhenPinned = nowExpanded;
159                 } else {
160                     nowExpanded = !isExpanded();
161                     setUserExpanded(nowExpanded);
162                 }
163                 notifyHeightChanged(true);
164                 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
165                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER,
166                         nowExpanded);
167             }
168         }
169     };
170     private boolean mForceUnlocked;
171     private boolean mDismissed;
172     private boolean mKeepInParent;
173     private boolean mRemoved;
174     private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
175             new FloatProperty<ExpandableNotificationRow>("translate") {
176                 @Override
177                 public void setValue(ExpandableNotificationRow object, float value) {
178                     object.setTranslation(value);
179                 }
180 
181                 @Override
182                 public Float get(ExpandableNotificationRow object) {
183                     return object.getTranslation();
184                 }
185     };
186     private OnClickListener mOnClickListener;
187     private boolean mHeadsupDisappearRunning;
188     private View mChildAfterViewWhenDismissed;
189     private View mGroupParentWhenDismissed;
190     private boolean mRefocusOnDismiss;
191 
isGroupExpansionChanging()192     public boolean isGroupExpansionChanging() {
193         if (isChildInGroup()) {
194             return mNotificationParent.isGroupExpansionChanging();
195         }
196         return mGroupExpansionChanging;
197     }
198 
setGroupExpansionChanging(boolean changing)199     public void setGroupExpansionChanging(boolean changing) {
200         mGroupExpansionChanging = changing;
201     }
202 
203     @Override
setActualHeightAnimating(boolean animating)204     public void setActualHeightAnimating(boolean animating) {
205         if (mPrivateLayout != null) {
206             mPrivateLayout.setContentHeightAnimating(animating);
207         }
208     }
209 
getPrivateLayout()210     public NotificationContentView getPrivateLayout() {
211         return mPrivateLayout;
212     }
213 
getPublicLayout()214     public NotificationContentView getPublicLayout() {
215         return mPublicLayout;
216     }
217 
setIconAnimationRunning(boolean running)218     public void setIconAnimationRunning(boolean running) {
219         setIconAnimationRunning(running, mPublicLayout);
220         setIconAnimationRunning(running, mPrivateLayout);
221         if (mIsSummaryWithChildren) {
222             setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
223             List<ExpandableNotificationRow> notificationChildren =
224                     mChildrenContainer.getNotificationChildren();
225             for (int i = 0; i < notificationChildren.size(); i++) {
226                 ExpandableNotificationRow child = notificationChildren.get(i);
227                 child.setIconAnimationRunning(running);
228             }
229         }
230         mIconAnimationRunning = running;
231     }
232 
setIconAnimationRunning(boolean running, NotificationContentView layout)233     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
234         if (layout != null) {
235             View contractedChild = layout.getContractedChild();
236             View expandedChild = layout.getExpandedChild();
237             View headsUpChild = layout.getHeadsUpChild();
238             setIconAnimationRunningForChild(running, contractedChild);
239             setIconAnimationRunningForChild(running, expandedChild);
240             setIconAnimationRunningForChild(running, headsUpChild);
241         }
242     }
243 
setIconAnimationRunningForChild(boolean running, View child)244     private void setIconAnimationRunningForChild(boolean running, View child) {
245         if (child != null) {
246             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
247             setIconRunning(icon, running);
248             ImageView rightIcon = (ImageView) child.findViewById(
249                     com.android.internal.R.id.right_icon);
250             setIconRunning(rightIcon, running);
251         }
252     }
253 
setIconRunning(ImageView imageView, boolean running)254     private void setIconRunning(ImageView imageView, boolean running) {
255         if (imageView != null) {
256             Drawable drawable = imageView.getDrawable();
257             if (drawable instanceof AnimationDrawable) {
258                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
259                 if (running) {
260                     animationDrawable.start();
261                 } else {
262                     animationDrawable.stop();
263                 }
264             } else if (drawable instanceof AnimatedVectorDrawable) {
265                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
266                 if (running) {
267                     animationDrawable.start();
268                 } else {
269                     animationDrawable.stop();
270                 }
271             }
272         }
273     }
274 
onNotificationUpdated(NotificationData.Entry entry)275     public void onNotificationUpdated(NotificationData.Entry entry) {
276         mEntry = entry;
277         mStatusBarNotification = entry.notification;
278         mPrivateLayout.onNotificationUpdated(entry);
279         mPublicLayout.onNotificationUpdated(entry);
280         mShowingPublicInitialized = false;
281         updateNotificationColor();
282         if (mIsSummaryWithChildren) {
283             mChildrenContainer.recreateNotificationHeader(mExpandClickListener, mEntry.notification);
284             mChildrenContainer.onNotificationUpdated();
285         }
286         if (mIconAnimationRunning) {
287             setIconAnimationRunning(true);
288         }
289         if (mNotificationParent != null) {
290             mNotificationParent.updateChildrenHeaderAppearance();
291         }
292         onChildrenCountChanged();
293         // The public layouts expand button is always visible
294         mPublicLayout.updateExpandButtons(true);
295         updateLimits();
296     }
297 
updateLimits()298     private void updateLimits() {
299         updateLimitsForView(mPrivateLayout);
300         updateLimitsForView(mPublicLayout);
301     }
302 
updateLimitsForView(NotificationContentView layout)303     private void updateLimitsForView(NotificationContentView layout) {
304         boolean customView = layout.getContractedChild().getId()
305                 != com.android.internal.R.id.status_bar_latest_event_content;
306         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
307         int minHeight = customView && beforeN && !mIsSummaryWithChildren ?
308                 mNotificationMinHeightLegacy : mNotificationMinHeight;
309         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
310                 layout.getHeadsUpChild().getId()
311                         != com.android.internal.R.id.status_bar_latest_event_content;
312         int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy
313                 : mMaxHeadsUpHeight;
314         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight);
315     }
316 
317     public StatusBarNotification getStatusBarNotification() {
318         return mStatusBarNotification;
319     }
320 
321     public boolean isHeadsUp() {
322         return mIsHeadsUp;
323     }
324 
325     public void setHeadsUp(boolean isHeadsUp) {
326         int intrinsicBefore = getIntrinsicHeight();
327         mIsHeadsUp = isHeadsUp;
328         mPrivateLayout.setHeadsUp(isHeadsUp);
329         if (mIsSummaryWithChildren) {
330             // The overflow might change since we allow more lines as HUN.
331             mChildrenContainer.updateGroupOverflow();
332         }
333         if (intrinsicBefore != getIntrinsicHeight()) {
334             notifyHeightChanged(false  /* needsAnimation */);
335         }
336     }
337 
338     public void setGroupManager(NotificationGroupManager groupManager) {
339         mGroupManager = groupManager;
340         mPrivateLayout.setGroupManager(groupManager);
341     }
342 
343     public void setRemoteInputController(RemoteInputController r) {
344         mPrivateLayout.setRemoteInputController(r);
345     }
346 
347     public void setAppName(String appName) {
348         mAppName = appName;
349         if (mSettingsIconRow != null) {
350             mSettingsIconRow.setAppName(mAppName);
351         }
352     }
353 
354     public void addChildNotification(ExpandableNotificationRow row) {
355         addChildNotification(row, -1);
356     }
357 
358     /**
359      * Add a child notification to this view.
360      *
361      * @param row the row to add
362      * @param childIndex the index to add it at, if -1 it will be added at the end
363      */
364     public void addChildNotification(ExpandableNotificationRow row, int childIndex) {
365         if (mChildrenContainer == null) {
366             mChildrenContainerStub.inflate();
367         }
368         mChildrenContainer.addNotification(row, childIndex);
369         onChildrenCountChanged();
370         row.setIsChildInGroup(true, this);
371     }
372 
373     public void removeChildNotification(ExpandableNotificationRow row) {
374         if (mChildrenContainer != null) {
375             mChildrenContainer.removeNotification(row);
376         }
377         onChildrenCountChanged();
378         row.setIsChildInGroup(false, null);
379     }
380 
381     public boolean isChildInGroup() {
382         return mNotificationParent != null;
383     }
384 
385     public ExpandableNotificationRow getNotificationParent() {
386         return mNotificationParent;
387     }
388 
389     /**
390      * @param isChildInGroup Is this notification now in a group
391      * @param parent the new parent notification
392      */
393     public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {;
394         boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup;
395         mNotificationParent = childInGroup ? parent : null;
396         mPrivateLayout.setIsChildInGroup(childInGroup);
397         resetBackgroundAlpha();
398         updateBackgroundForGroupState();
399         updateClickAndFocus();
400         if (mNotificationParent != null) {
401             mNotificationParent.updateBackgroundForGroupState();
402         }
403     }
404 
405     @Override
406     public boolean onTouchEvent(MotionEvent event) {
407         if (event.getActionMasked() != MotionEvent.ACTION_DOWN
408                 || !isChildInGroup() || isGroupExpanded()) {
409             return super.onTouchEvent(event);
410         } else {
411             return false;
412         }
413     }
414 
415     @Override
416     protected boolean handleSlideBack() {
417         if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
418             animateTranslateNotification(0 /* targetLeft */);
419             return true;
420         }
421         return false;
422     }
423 
424     @Override
425     protected boolean shouldHideBackground() {
426         return super.shouldHideBackground() || mShowNoBackground;
427     }
428 
429     @Override
430     public boolean isSummaryWithChildren() {
431         return mIsSummaryWithChildren;
432     }
433 
434     @Override
435     public boolean areChildrenExpanded() {
436         return mChildrenExpanded;
437     }
438 
439     public List<ExpandableNotificationRow> getNotificationChildren() {
440         return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
441     }
442 
443     public int getNumberOfNotificationChildren() {
444         if (mChildrenContainer == null) {
445             return 0;
446         }
447         return mChildrenContainer.getNotificationChildren().size();
448     }
449 
450     /**
451      * Apply the order given in the list to the children.
452      *
453      * @param childOrder the new list order
454      * @return whether the list order has changed
455      */
456     public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) {
457         return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder);
458     }
459 
460     public void getChildrenStates(StackScrollState resultState) {
461         if (mIsSummaryWithChildren) {
462             StackViewState parentState = resultState.getViewStateForView(this);
463             mChildrenContainer.getState(resultState, parentState);
464         }
465     }
466 
467     public void applyChildrenState(StackScrollState state) {
468         if (mIsSummaryWithChildren) {
469             mChildrenContainer.applyState(state);
470         }
471     }
472 
473     public void prepareExpansionChanged(StackScrollState state) {
474         if (mIsSummaryWithChildren) {
475             mChildrenContainer.prepareExpansionChanged(state);
476         }
477     }
478 
479     public void startChildAnimation(StackScrollState finalState,
480             StackStateAnimator stateAnimator, long delay, long duration) {
481         if (mIsSummaryWithChildren) {
482             mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay,
483                     duration);
484         }
485     }
486 
487     public ExpandableNotificationRow getViewAtPosition(float y) {
488         if (!mIsSummaryWithChildren || !mChildrenExpanded) {
489             return this;
490         } else {
491             ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y);
492             return view == null ? this : view;
493         }
494     }
495 
496     public NotificationGuts getGuts() {
497         return mGuts;
498     }
499 
500     /**
501      * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
502      * the notification will be rendered on top of the screen.
503      *
504      * @param pinned whether it is pinned
505      */
506     public void setPinned(boolean pinned) {
507         int intrinsicHeight = getIntrinsicHeight();
508         mIsPinned = pinned;
509         if (intrinsicHeight != getIntrinsicHeight()) {
510             notifyHeightChanged(false);
511         }
512         if (pinned) {
513             setIconAnimationRunning(true);
514             mExpandedWhenPinned = false;
515         } else if (mExpandedWhenPinned) {
516             setUserExpanded(true);
517         }
518         setChronometerRunning(mLastChronometerRunning);
519     }
520 
521     public boolean isPinned() {
522         return mIsPinned;
523     }
524 
525     /**
526      * @param atLeastMinHeight should the value returned be at least the minimum height.
527      *                         Used to avoid cyclic calls
528      * @return the height of the heads up notification when pinned
529      */
530     public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
531         if (mIsSummaryWithChildren) {
532             return mChildrenContainer.getIntrinsicHeight();
533         }
534         if(mExpandedWhenPinned) {
535             return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
536         } else if (atLeastMinHeight) {
537             return Math.max(getCollapsedHeight(), mHeadsUpHeight);
538         } else {
539             return mHeadsUpHeight;
540         }
541     }
542 
543     /**
544      * Mark whether this notification was just clicked, i.e. the user has just clicked this
545      * notification in this frame.
546      */
547     public void setJustClicked(boolean justClicked) {
548         mJustClicked = justClicked;
549     }
550 
551     /**
552      * @return true if this notification has been clicked in this frame, false otherwise
553      */
554     public boolean wasJustClicked() {
555         return mJustClicked;
556     }
557 
558     public void setChronometerRunning(boolean running) {
559         mLastChronometerRunning = running;
560         setChronometerRunning(running, mPrivateLayout);
561         setChronometerRunning(running, mPublicLayout);
562         if (mChildrenContainer != null) {
563             List<ExpandableNotificationRow> notificationChildren =
564                     mChildrenContainer.getNotificationChildren();
565             for (int i = 0; i < notificationChildren.size(); i++) {
566                 ExpandableNotificationRow child = notificationChildren.get(i);
567                 child.setChronometerRunning(running);
568             }
569         }
570     }
571 
572     private void setChronometerRunning(boolean running, NotificationContentView layout) {
573         if (layout != null) {
574             running = running || isPinned();
575             View contractedChild = layout.getContractedChild();
576             View expandedChild = layout.getExpandedChild();
577             View headsUpChild = layout.getHeadsUpChild();
578             setChronometerRunningForChild(running, contractedChild);
579             setChronometerRunningForChild(running, expandedChild);
580             setChronometerRunningForChild(running, headsUpChild);
581         }
582     }
583 
584     private void setChronometerRunningForChild(boolean running, View child) {
585         if (child != null) {
586             View chronometer = child.findViewById(com.android.internal.R.id.chronometer);
587             if (chronometer instanceof Chronometer) {
588                 ((Chronometer) chronometer).setStarted(running);
589             }
590         }
591     }
592 
593     public NotificationHeaderView getNotificationHeader() {
594         if (mIsSummaryWithChildren) {
595             return mChildrenContainer.getHeaderView();
596         }
597         return mPrivateLayout.getNotificationHeader();
598     }
599 
600     private NotificationHeaderView getVisibleNotificationHeader() {
601         if (mIsSummaryWithChildren && !mShowingPublic) {
602             return mChildrenContainer.getHeaderView();
603         }
604         return getShowingLayout().getVisibleNotificationHeader();
605     }
606 
607     public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
608         mOnExpandClickListener = onExpandClickListener;
609     }
610 
611     @Override
612     public void setOnClickListener(@Nullable OnClickListener l) {
613         super.setOnClickListener(l);
614         mOnClickListener = l;
615         updateClickAndFocus();
616     }
617 
618     private void updateClickAndFocus() {
619         boolean normalChild = !isChildInGroup() || isGroupExpanded();
620         boolean clickable = mOnClickListener != null && normalChild;
621         if (isFocusable() != normalChild) {
622             setFocusable(normalChild);
623         }
624         if (isClickable() != clickable) {
625             setClickable(clickable);
626         }
627     }
628 
629     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
630         mHeadsUpManager = headsUpManager;
631     }
632 
633     public void reInflateViews() {
634         initDimens();
635         if (mIsSummaryWithChildren) {
636             if (mChildrenContainer != null) {
637                 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification);
638             }
639         }
640         if (mGuts != null) {
641             View oldGuts = mGuts;
642             int index = indexOfChild(oldGuts);
643             removeView(oldGuts);
644             mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate(
645                     R.layout.notification_guts, this, false);
646             mGuts.setVisibility(oldGuts.getVisibility());
647             addView(mGuts, index);
648         }
649         if (mSettingsIconRow != null) {
650             View oldSettings = mSettingsIconRow;
651             int settingsIndex = indexOfChild(oldSettings);
652             removeView(oldSettings);
653             mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
654                     R.layout.notification_settings_icon_row, this, false);
655             mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
656             mSettingsIconRow.setAppName(mAppName);
657             mSettingsIconRow.setVisibility(oldSettings.getVisibility());
658             addView(mSettingsIconRow, settingsIndex);
659 
660         }
661         mPrivateLayout.reInflateViews();
662         mPublicLayout.reInflateViews();
663     }
664 
665     public void setContentBackground(int customBackgroundColor, boolean animate,
666             NotificationContentView notificationContentView) {
667         if (getShowingLayout() == notificationContentView) {
668             setTintColor(customBackgroundColor, animate);
669         }
670     }
671 
672     public void closeRemoteInput() {
673         mPrivateLayout.closeRemoteInput();
674         mPublicLayout.closeRemoteInput();
675     }
676 
677     /**
678      * Set by how much the single line view should be indented.
679      */
680     public void setSingleLineWidthIndention(int indention) {
681         mPrivateLayout.setSingleLineWidthIndention(indention);
682     }
683 
684     public int getNotificationColor() {
685         return mNotificationColor;
686     }
687 
688     private void updateNotificationColor() {
689         mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext,
690                 getStatusBarNotification().getNotification().color);
691     }
692 
693     public HybridNotificationView getSingleLineView() {
694         return mPrivateLayout.getSingleLineView();
695     }
696 
697     public boolean isOnKeyguard() {
698         return mOnKeyguard;
699     }
700 
701     public void removeAllChildren() {
702         List<ExpandableNotificationRow> notificationChildren
703                 = mChildrenContainer.getNotificationChildren();
704         ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
705         for (int i = 0; i < clonedList.size(); i++) {
706             ExpandableNotificationRow row = clonedList.get(i);
707             if (row.keepInParent()) {
708                 continue;
709             }
710             mChildrenContainer.removeNotification(row);
711             row.setIsChildInGroup(false, null);
712         }
713         onChildrenCountChanged();
714     }
715 
716     public void setForceUnlocked(boolean forceUnlocked) {
717         mForceUnlocked = forceUnlocked;
718         if (mIsSummaryWithChildren) {
719             List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
720             for (ExpandableNotificationRow child : notificationChildren) {
721                 child.setForceUnlocked(forceUnlocked);
722             }
723         }
724     }
725 
726     public void setDismissed(boolean dismissed, boolean fromAccessibility) {
727         mDismissed = dismissed;
728         mGroupParentWhenDismissed = mNotificationParent;
729         mRefocusOnDismiss = fromAccessibility;
730         mChildAfterViewWhenDismissed = null;
731         if (isChildInGroup()) {
732             List<ExpandableNotificationRow> notificationChildren =
733                     mNotificationParent.getNotificationChildren();
734             int i = notificationChildren.indexOf(this);
735             if (i != -1 && i < notificationChildren.size() - 1) {
736                 mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
737             }
738         }
739     }
740 
741     public boolean isDismissed() {
742         return mDismissed;
743     }
744 
745     public boolean keepInParent() {
746         return mKeepInParent;
747     }
748 
749     public void setKeepInParent(boolean keepInParent) {
750         mKeepInParent = keepInParent;
751     }
752 
753     public boolean isRemoved() {
754         return mRemoved;
755     }
756 
757     public void setRemoved() {
758         mRemoved = true;
759 
760         mPrivateLayout.setRemoved();
761     }
762 
763     public NotificationChildrenContainer getChildrenContainer() {
764         return mChildrenContainer;
765     }
766 
767     public void setHeadsupDisappearRunning(boolean running) {
768         mHeadsupDisappearRunning = running;
769         mPrivateLayout.setHeadsupDisappearRunning(running);
770     }
771 
772     public View getChildAfterViewWhenDismissed() {
773         return mChildAfterViewWhenDismissed;
774     }
775 
776     public View getGroupParentWhenDismissed() {
777         return mGroupParentWhenDismissed;
778     }
779 
780     public void performDismiss() {
781         mVetoButton.performClick();
782     }
783 
784     public void setOnDismissListener(OnClickListener listener) {
785         mVetoButton.setOnClickListener(listener);
786     }
787 
788     public interface ExpansionLogger {
789         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
790     }
791 
792     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
793         super(context, attrs);
794         mFalsingManager = FalsingManager.getInstance(context);
795         initDimens();
796     }
797 
798     private void initDimens() {
799         mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
800         mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
801         mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
802         mMaxHeadsUpHeightLegacy = getFontScaledHeight(
803                 R.dimen.notification_max_heads_up_height_legacy);
804         mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
805         mIncreasedPaddingBetweenElements = getResources()
806                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
807     }
808 
809     /**
810      * @param dimenId the dimen to look up
811      * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
812      */
813     private int getFontScaledHeight(int dimenId) {
814         int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
815         float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
816                 getResources().getDisplayMetrics().density);
817         return (int) (dimensionPixelSize * factor);
818     }
819 
820     /**
821      * Resets this view so it can be re-used for an updated notification.
822      */
823     @Override
824     public void reset() {
825         super.reset();
826         final boolean wasExpanded = isExpanded();
827         mExpandable = false;
828         mHasUserChangedExpansion = false;
829         mUserLocked = false;
830         mShowingPublic = false;
831         mSensitive = false;
832         mShowingPublicInitialized = false;
833         mIsSystemExpanded = false;
834         mOnKeyguard = false;
835         mPublicLayout.reset();
836         mPrivateLayout.reset();
837         resetHeight();
838         resetTranslation();
839         logExpansionEvent(false, wasExpanded);
840     }
841 
842     public void resetHeight() {
843         mMaxExpandHeight = 0;
844         mHeadsUpHeight = 0;
845         onHeightReset();
846         requestLayout();
847     }
848 
849     @Override
850     protected void onFinishInflate() {
851         super.onFinishInflate();
852         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
853         mPublicLayout.setContainingNotification(this);
854         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
855         mPrivateLayout.setExpandClickListener(mExpandClickListener);
856         mPrivateLayout.setContainingNotification(this);
857         mPublicLayout.setExpandClickListener(mExpandClickListener);
858         mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
859         mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
860             @Override
861             public void onInflate(ViewStub stub, View inflated) {
862                 mSettingsIconRow = (NotificationSettingsIconRow) inflated;
863                 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
864                 mSettingsIconRow.setAppName(mAppName);
865             }
866         });
867         mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
868         mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
869             @Override
870             public void onInflate(ViewStub stub, View inflated) {
871                 mGuts = (NotificationGuts) inflated;
872                 mGuts.setClipTopAmount(getClipTopAmount());
873                 mGuts.setActualHeight(getActualHeight());
874                 mGutsStub = null;
875             }
876         });
877         mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
878         mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
879 
880             @Override
881             public void onInflate(ViewStub stub, View inflated) {
882                 mChildrenContainer = (NotificationChildrenContainer) inflated;
883                 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this);
884                 mChildrenContainer.onNotificationUpdated();
885                 mTranslateableViews.add(mChildrenContainer);
886             }
887         });
888         mVetoButton = findViewById(R.id.veto);
889         mVetoButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
890         mVetoButton.setContentDescription(mContext.getString(
891                 R.string.accessibility_remove_notification));
892 
893         // Add the views that we translate to reveal the gear
894         mTranslateableViews = new ArrayList<View>();
895         for (int i = 0; i < getChildCount(); i++) {
896             mTranslateableViews.add(getChildAt(i));
897         }
898         // Remove views that don't translate
899         mTranslateableViews.remove(mVetoButton);
900         mTranslateableViews.remove(mSettingsIconRowStub);
901         mTranslateableViews.remove(mChildrenContainerStub);
902         mTranslateableViews.remove(mGutsStub);
903     }
904 
905     public View getVetoButton() {
906         return mVetoButton;
907     }
908 
909     public void resetTranslation() {
910         if (mTranslateableViews != null) {
911             for (int i = 0; i < mTranslateableViews.size(); i++) {
912                 mTranslateableViews.get(i).setTranslationX(0);
913             }
914         }
915         invalidateOutline();
916         if (mSettingsIconRow != null) {
917             mSettingsIconRow.resetState();
918         }
919     }
920 
921     public void animateTranslateNotification(final float leftTarget) {
922         if (mTranslateAnim != null) {
923             mTranslateAnim.cancel();
924         }
925         mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */);
926         if (mTranslateAnim != null) {
927             mTranslateAnim.start();
928         }
929     }
930 
931     @Override
932     public void setTranslation(float translationX) {
933         if (areGutsExposed()) {
934             // Don't translate if guts are showing.
935             return;
936         }
937         // Translate the group of views
938         for (int i = 0; i < mTranslateableViews.size(); i++) {
939             if (mTranslateableViews.get(i) != null) {
940                 mTranslateableViews.get(i).setTranslationX(translationX);
941             }
942         }
943         invalidateOutline();
944         if (mSettingsIconRow != null) {
945             mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
946         }
947     }
948 
949     @Override
950     public float getTranslation() {
951         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
952             // All of the views in the list should have same translation, just use first one.
953             return mTranslateableViews.get(0).getTranslationX();
954         }
955         return 0;
956     }
957 
958     public Animator getTranslateViewAnimator(final float leftTarget,
959             AnimatorUpdateListener listener) {
960         if (mTranslateAnim != null) {
961             mTranslateAnim.cancel();
962         }
963         if (areGutsExposed()) {
964             // No translation if guts are exposed.
965             return null;
966         }
967         final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT,
968                 leftTarget);
969         if (listener != null) {
970             translateAnim.addUpdateListener(listener);
971         }
972         translateAnim.addListener(new AnimatorListenerAdapter() {
973             boolean cancelled = false;
974 
975             @Override
976             public void onAnimationCancel(Animator anim) {
977                 cancelled = true;
978             }
979 
980             @Override
981             public void onAnimationEnd(Animator anim) {
982                 if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
983                     mSettingsIconRow.resetState();
984                     mTranslateAnim = null;
985                 }
986             }
987         });
988         mTranslateAnim = translateAnim;
989         return translateAnim;
990     }
991 
992     public float getSpaceForGear() {
993         if (mSettingsIconRow != null) {
994             return mSettingsIconRow.getSpaceForGear();
995         }
996         return 0;
997     }
998 
999     public NotificationSettingsIconRow getSettingsRow() {
1000         if (mSettingsIconRow == null) {
1001             mSettingsIconRowStub.inflate();
1002         }
1003         return mSettingsIconRow;
1004     }
1005 
1006     public void inflateGuts() {
1007         if (mGuts == null) {
1008             mGutsStub.inflate();
1009         }
1010     }
1011 
1012     private void updateChildrenVisibility() {
1013         mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE
1014                 : INVISIBLE);
1015         if (mChildrenContainer != null) {
1016             mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE
1017                     : INVISIBLE);
1018             mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren
1019                     ? VISIBLE
1020                     : INVISIBLE);
1021         }
1022         // The limits might have changed if the view suddenly became a group or vice versa
1023         updateLimits();
1024     }
1025 
1026     @Override
1027     public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
1028         if (super.onRequestSendAccessibilityEventInternal(child, event)) {
1029             // Add a record for the entire layout since its content is somehow small.
1030             // The event comes from a leaf view that is interacted with.
1031             AccessibilityEvent record = AccessibilityEvent.obtain();
1032             onInitializeAccessibilityEvent(record);
1033             dispatchPopulateAccessibilityEvent(record);
1034             event.appendRecord(record);
1035             return true;
1036         }
1037         return false;
1038     }
1039 
1040     @Override
1041     public void setDark(boolean dark, boolean fade, long delay) {
1042         super.setDark(dark, fade, delay);
1043         final NotificationContentView showing = getShowingLayout();
1044         if (showing != null) {
1045             showing.setDark(dark, fade, delay);
1046         }
1047         if (mIsSummaryWithChildren) {
1048             mChildrenContainer.setDark(dark, fade, delay);
1049         }
1050     }
1051 
1052     public boolean isExpandable() {
1053         if (mIsSummaryWithChildren && !mShowingPublic) {
1054             return !mChildrenExpanded;
1055         }
1056         return mExpandable;
1057     }
1058 
1059     public void setExpandable(boolean expandable) {
1060         mExpandable = expandable;
1061         mPrivateLayout.updateExpandButtons(isExpandable());
1062     }
1063 
1064     @Override
1065     public void setClipToActualHeight(boolean clipToActualHeight) {
1066         super.setClipToActualHeight(clipToActualHeight || isUserLocked());
1067         getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked());
1068     }
1069 
1070     /**
1071      * @return whether the user has changed the expansion state
1072      */
1073     public boolean hasUserChangedExpansion() {
1074         return mHasUserChangedExpansion;
1075     }
1076 
1077     public boolean isUserExpanded() {
1078         return mUserExpanded;
1079     }
1080 
1081     /**
1082      * Set this notification to be expanded by the user
1083      *
1084      * @param userExpanded whether the user wants this notification to be expanded
1085      */
1086     public void setUserExpanded(boolean userExpanded) {
1087         setUserExpanded(userExpanded, false /* allowChildExpansion */);
1088     }
1089 
1090     /**
1091      * Set this notification to be expanded by the user
1092      *
1093      * @param userExpanded whether the user wants this notification to be expanded
1094      * @param allowChildExpansion whether a call to this method allows expanding children
1095      */
1096     public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) {
1097         mFalsingManager.setNotificationExpanded();
1098         if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) {
1099             final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
1100             mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded);
1101             logExpansionEvent(true /* userAction */, wasExpanded);
1102             return;
1103         }
1104         if (userExpanded && !mExpandable) return;
1105         final boolean wasExpanded = isExpanded();
1106         mHasUserChangedExpansion = true;
1107         mUserExpanded = userExpanded;
1108         logExpansionEvent(true, wasExpanded);
1109     }
1110 
1111     public void resetUserExpansion() {
1112         mHasUserChangedExpansion = false;
1113         mUserExpanded = false;
1114     }
1115 
1116     public boolean isUserLocked() {
1117         return mUserLocked && !mForceUnlocked;
1118     }
1119 
1120     public void setUserLocked(boolean userLocked) {
1121         mUserLocked = userLocked;
1122         mPrivateLayout.setUserExpanding(userLocked);
1123         if (mIsSummaryWithChildren) {
1124             mChildrenContainer.setUserLocked(userLocked);
1125             if (userLocked || !isGroupExpanded()) {
1126                 updateBackgroundForGroupState();
1127             }
1128         }
1129     }
1130 
1131     /**
1132      * @return has the system set this notification to be expanded
1133      */
1134     public boolean isSystemExpanded() {
1135         return mIsSystemExpanded;
1136     }
1137 
1138     /**
1139      * Set this notification to be expanded by the system.
1140      *
1141      * @param expand whether the system wants this notification to be expanded.
1142      */
1143     public void setSystemExpanded(boolean expand) {
1144         if (expand != mIsSystemExpanded) {
1145             final boolean wasExpanded = isExpanded();
1146             mIsSystemExpanded = expand;
1147             notifyHeightChanged(false /* needsAnimation */);
1148             logExpansionEvent(false, wasExpanded);
1149             if (mIsSummaryWithChildren) {
1150                 mChildrenContainer.updateGroupOverflow();
1151             }
1152         }
1153     }
1154 
1155     /**
1156      * @param onKeyguard whether to prevent notification expansion
1157      */
1158     public void setOnKeyguard(boolean onKeyguard) {
1159         if (onKeyguard != mOnKeyguard) {
1160             final boolean wasExpanded = isExpanded();
1161             mOnKeyguard = onKeyguard;
1162             logExpansionEvent(false, wasExpanded);
1163             if (wasExpanded != isExpanded()) {
1164                 if (mIsSummaryWithChildren) {
1165                     mChildrenContainer.updateGroupOverflow();
1166                 }
1167                 notifyHeightChanged(false /* needsAnimation */);
1168             }
1169         }
1170     }
1171 
1172     /**
1173      * @return Can the underlying notification be cleared? This can be different from whether the
1174      *         notification can be dismissed in case notifications are sensitive on the lockscreen.
1175      * @see #canViewBeDismissed()
1176      */
1177     public boolean isClearable() {
1178         if (mStatusBarNotification == null || !mStatusBarNotification.isClearable()) {
1179             return false;
1180         }
1181         if (mIsSummaryWithChildren) {
1182             List<ExpandableNotificationRow> notificationChildren =
1183                     mChildrenContainer.getNotificationChildren();
1184             for (int i = 0; i < notificationChildren.size(); i++) {
1185                 ExpandableNotificationRow child = notificationChildren.get(i);
1186                 if (!child.isClearable()) {
1187                     return false;
1188                 }
1189             }
1190         }
1191         return true;
1192     }
1193 
1194     @Override
1195     public int getIntrinsicHeight() {
1196         if (isUserLocked()) {
1197             return getActualHeight();
1198         }
1199         if (mGuts != null && mGuts.areGutsExposed()) {
1200             return mGuts.getHeight();
1201         } else if ((isChildInGroup() && !isGroupExpanded())) {
1202             return mPrivateLayout.getMinHeight();
1203         } else if (mSensitive && mHideSensitiveForIntrinsicHeight) {
1204             return getMinHeight();
1205         } else if (mIsSummaryWithChildren && !mOnKeyguard) {
1206             return mChildrenContainer.getIntrinsicHeight();
1207         } else if (mIsHeadsUp || mHeadsupDisappearRunning) {
1208             if (isPinned() || mHeadsupDisappearRunning) {
1209                 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
1210             } else if (isExpanded()) {
1211                 return Math.max(getMaxExpandHeight(), mHeadsUpHeight);
1212             } else {
1213                 return Math.max(getCollapsedHeight(), mHeadsUpHeight);
1214             }
1215         } else if (isExpanded()) {
1216             return getMaxExpandHeight();
1217         } else {
1218             return getCollapsedHeight();
1219         }
1220     }
1221 
1222     public boolean isGroupExpanded() {
1223         return mGroupManager.isGroupExpanded(mStatusBarNotification);
1224     }
1225 
1226     private void onChildrenCountChanged() {
1227         mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS
1228                 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0;
1229         if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
1230             mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
1231                     mEntry.notification);
1232         }
1233         getShowingLayout().updateBackgroundColor(false /* animate */);
1234         mPrivateLayout.updateExpandButtons(isExpandable());
1235         updateChildrenHeaderAppearance();
1236         updateChildrenVisibility();
1237     }
1238 
1239     public void updateChildrenHeaderAppearance() {
1240         if (mIsSummaryWithChildren) {
1241             mChildrenContainer.updateChildrenHeaderAppearance();
1242         }
1243     }
1244 
1245     /**
1246      * Check whether the view state is currently expanded. This is given by the system in {@link
1247      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
1248      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
1249      * view can differ from this state, if layout params are modified from outside.
1250      *
1251      * @return whether the view state is currently expanded.
1252      */
1253     public boolean isExpanded() {
1254         return isExpanded(false /* allowOnKeyguard */);
1255     }
1256 
1257     public boolean isExpanded(boolean allowOnKeyguard) {
1258         return (!mOnKeyguard || allowOnKeyguard)
1259                 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
1260                 || isUserExpanded());
1261     }
1262 
1263     private boolean isSystemChildExpanded() {
1264         return mIsSystemChildExpanded;
1265     }
1266 
1267     public void setSystemChildExpanded(boolean expanded) {
1268         mIsSystemChildExpanded = expanded;
1269     }
1270 
1271     @Override
1272     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1273         super.onLayout(changed, left, top, right, bottom);
1274         updateMaxHeights();
1275         if (mSettingsIconRow != null) {
1276             mSettingsIconRow.updateVerticalLocation();
1277         }
1278     }
1279 
1280     private void updateMaxHeights() {
1281         int intrinsicBefore = getIntrinsicHeight();
1282         View expandedChild = mPrivateLayout.getExpandedChild();
1283         if (expandedChild == null) {
1284             expandedChild = mPrivateLayout.getContractedChild();
1285         }
1286         mMaxExpandHeight = expandedChild.getHeight();
1287         View headsUpChild = mPrivateLayout.getHeadsUpChild();
1288         if (headsUpChild == null) {
1289             headsUpChild = mPrivateLayout.getContractedChild();
1290         }
1291         mHeadsUpHeight = headsUpChild.getHeight();
1292         if (intrinsicBefore != getIntrinsicHeight()) {
1293             notifyHeightChanged(false  /* needsAnimation */);
1294         }
1295     }
1296 
1297     @Override
1298     public void notifyHeightChanged(boolean needsAnimation) {
1299         super.notifyHeightChanged(needsAnimation);
1300         getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked());
1301     }
1302 
1303     public void setSensitive(boolean sensitive, boolean hideSensitive) {
1304         mSensitive = sensitive;
1305         mSensitiveHiddenInGeneral = hideSensitive;
1306     }
1307 
1308     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
1309         mHideSensitiveForIntrinsicHeight = hideSensitive;
1310         if (mIsSummaryWithChildren) {
1311             List<ExpandableNotificationRow> notificationChildren =
1312                     mChildrenContainer.getNotificationChildren();
1313             for (int i = 0; i < notificationChildren.size(); i++) {
1314                 ExpandableNotificationRow child = notificationChildren.get(i);
1315                 child.setHideSensitiveForIntrinsicHeight(hideSensitive);
1316             }
1317         }
1318     }
1319 
1320     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
1321             long duration) {
1322         boolean oldShowingPublic = mShowingPublic;
1323         mShowingPublic = mSensitive && hideSensitive;
1324         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
1325             return;
1326         }
1327 
1328         // bail out if no public version
1329         if (mPublicLayout.getChildCount() == 0) return;
1330 
1331         if (!animated) {
1332             mPublicLayout.animate().cancel();
1333             mPrivateLayout.animate().cancel();
1334             if (mChildrenContainer != null) {
1335                 mChildrenContainer.animate().cancel();
1336                 mChildrenContainer.setAlpha(1f);
1337             }
1338             mPublicLayout.setAlpha(1f);
1339             mPrivateLayout.setAlpha(1f);
1340             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
1341             updateChildrenVisibility();
1342         } else {
1343             animateShowingPublic(delay, duration);
1344         }
1345         NotificationContentView showingLayout = getShowingLayout();
1346         showingLayout.updateBackgroundColor(animated);
1347         mPrivateLayout.updateExpandButtons(isExpandable());
1348         mShowingPublicInitialized = true;
1349     }
1350 
1351     private void animateShowingPublic(long delay, long duration) {
1352         View[] privateViews = mIsSummaryWithChildren
1353                 ? new View[] {mChildrenContainer}
1354                 : new View[] {mPrivateLayout};
1355         View[] publicViews = new View[] {mPublicLayout};
1356         View[] hiddenChildren = mShowingPublic ? privateViews : publicViews;
1357         View[] shownChildren = mShowingPublic ? publicViews : privateViews;
1358         for (final View hiddenView : hiddenChildren) {
1359             hiddenView.setVisibility(View.VISIBLE);
1360             hiddenView.animate().cancel();
1361             hiddenView.animate()
1362                     .alpha(0f)
1363                     .setStartDelay(delay)
1364                     .setDuration(duration)
1365                     .withEndAction(new Runnable() {
1366                         @Override
1367                         public void run() {
1368                             hiddenView.setVisibility(View.INVISIBLE);
1369                         }
1370                     });
1371         }
1372         for (View showView : shownChildren) {
1373             showView.setVisibility(View.VISIBLE);
1374             showView.setAlpha(0f);
1375             showView.animate().cancel();
1376             showView.animate()
1377                     .alpha(1f)
1378                     .setStartDelay(delay)
1379                     .setDuration(duration);
1380         }
1381     }
1382 
1383     public boolean mustStayOnScreen() {
1384         return mIsHeadsUp;
1385     }
1386 
1387     /**
1388      * @return Whether this view is allowed to be dismissed. Only valid for visible notifications as
1389      *         otherwise some state might not be updated. To request about the general clearability
1390      *         see {@link #isClearable()}.
1391      */
1392     public boolean canViewBeDismissed() {
1393         return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral);
1394     }
1395 
1396     public void makeActionsVisibile() {
1397         setUserExpanded(true, true);
1398         if (isChildInGroup()) {
1399             mGroupManager.setGroupExpanded(mStatusBarNotification, true);
1400         }
1401         notifyHeightChanged(false);
1402     }
1403 
1404     public void setChildrenExpanded(boolean expanded, boolean animate) {
1405         mChildrenExpanded = expanded;
1406         if (mChildrenContainer != null) {
1407             mChildrenContainer.setChildrenExpanded(expanded);
1408         }
1409         updateBackgroundForGroupState();
1410         updateClickAndFocus();
1411     }
1412 
1413     public static void applyTint(View v, int color) {
1414         int alpha;
1415         if (color != 0) {
1416             alpha = COLORED_DIVIDER_ALPHA;
1417         } else {
1418             color = 0xff000000;
1419             alpha = DEFAULT_DIVIDER_ALPHA;
1420         }
1421         if (v.getBackground() instanceof ColorDrawable) {
1422             ColorDrawable background = (ColorDrawable) v.getBackground();
1423             background.mutate();
1424             background.setColor(color);
1425             background.setAlpha(alpha);
1426         }
1427     }
1428 
1429     public int getMaxExpandHeight() {
1430         return mMaxExpandHeight;
1431     }
1432 
1433     public boolean areGutsExposed() {
1434         return (mGuts != null && mGuts.areGutsExposed());
1435     }
1436 
1437     @Override
1438     public boolean isContentExpandable() {
1439         NotificationContentView showingLayout = getShowingLayout();
1440         return showingLayout.isContentExpandable();
1441     }
1442 
1443     @Override
1444     protected View getContentView() {
1445         if (mIsSummaryWithChildren && !mShowingPublic) {
1446             return mChildrenContainer;
1447         }
1448         return getShowingLayout();
1449     }
1450 
1451     @Override
1452     protected void onAppearAnimationFinished(boolean wasAppearing) {
1453         super.onAppearAnimationFinished(wasAppearing);
1454         if (wasAppearing) {
1455             // During the animation the visible view might have changed, so let's make sure all
1456             // alphas are reset
1457             if (mChildrenContainer != null) {
1458                 mChildrenContainer.setAlpha(1.0f);
1459                 mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
1460             }
1461             mPrivateLayout.setAlpha(1.0f);
1462             mPrivateLayout.setLayerType(LAYER_TYPE_NONE, null);
1463             mPublicLayout.setAlpha(1.0f);
1464             mPublicLayout.setLayerType(LAYER_TYPE_NONE, null);
1465         }
1466     }
1467 
1468     @Override
1469     public int getExtraBottomPadding() {
1470         if (mIsSummaryWithChildren && isGroupExpanded()) {
1471             return mIncreasedPaddingBetweenElements;
1472         }
1473         return 0;
1474     }
1475 
1476     @Override
1477     public void setActualHeight(int height, boolean notifyListeners) {
1478         super.setActualHeight(height, notifyListeners);
1479         if (mGuts != null && mGuts.areGutsExposed()) {
1480             mGuts.setActualHeight(height);
1481             return;
1482         }
1483         int contentHeight = Math.max(getMinHeight(), height);
1484         mPrivateLayout.setContentHeight(contentHeight);
1485         mPublicLayout.setContentHeight(contentHeight);
1486         if (mIsSummaryWithChildren) {
1487             mChildrenContainer.setActualHeight(height);
1488         }
1489         if (mGuts != null) {
1490             mGuts.setActualHeight(height);
1491         }
1492     }
1493 
1494     @Override
1495     public int getMaxContentHeight() {
1496         if (mIsSummaryWithChildren && !mShowingPublic) {
1497             return mChildrenContainer.getMaxContentHeight();
1498         }
1499         NotificationContentView showingLayout = getShowingLayout();
1500         return showingLayout.getMaxHeight();
1501     }
1502 
1503     @Override
1504     public int getMinHeight() {
1505         if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
1506                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
1507         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
1508             return mChildrenContainer.getMinHeight();
1509         } else if (mIsHeadsUp) {
1510             return mHeadsUpHeight;
1511         }
1512         NotificationContentView showingLayout = getShowingLayout();
1513         return showingLayout.getMinHeight();
1514     }
1515 
1516     @Override
1517     public int getCollapsedHeight() {
1518         if (mIsSummaryWithChildren && !mShowingPublic) {
1519             return mChildrenContainer.getCollapsedHeight();
1520         }
1521         return getMinHeight();
1522     }
1523 
1524     @Override
1525     public void setClipTopAmount(int clipTopAmount) {
1526         super.setClipTopAmount(clipTopAmount);
1527         mPrivateLayout.setClipTopAmount(clipTopAmount);
1528         mPublicLayout.setClipTopAmount(clipTopAmount);
1529         if (mGuts != null) {
1530             mGuts.setClipTopAmount(clipTopAmount);
1531         }
1532     }
1533 
1534     public boolean isMaxExpandHeightInitialized() {
1535         return mMaxExpandHeight != 0;
1536     }
1537 
1538     public NotificationContentView getShowingLayout() {
1539         return mShowingPublic ? mPublicLayout : mPrivateLayout;
1540     }
1541 
1542     @Override
1543     public void setShowingLegacyBackground(boolean showing) {
1544         super.setShowingLegacyBackground(showing);
1545         mPrivateLayout.setShowingLegacyBackground(showing);
1546         mPublicLayout.setShowingLegacyBackground(showing);
1547     }
1548 
1549     @Override
1550     protected void updateBackgroundTint() {
1551         super.updateBackgroundTint();
1552         updateBackgroundForGroupState();
1553         if (mIsSummaryWithChildren) {
1554             List<ExpandableNotificationRow> notificationChildren =
1555                     mChildrenContainer.getNotificationChildren();
1556             for (int i = 0; i < notificationChildren.size(); i++) {
1557                 ExpandableNotificationRow child = notificationChildren.get(i);
1558                 child.updateBackgroundForGroupState();
1559             }
1560         }
1561     }
1562 
1563     /**
1564      * Called when a group has finished animating from collapsed or expanded state.
1565      */
1566     public void onFinishedExpansionChange() {
1567         mGroupExpansionChanging = false;
1568         updateBackgroundForGroupState();
1569     }
1570 
1571     /**
1572      * Updates the parent and children backgrounds in a group based on the expansion state.
1573      */
1574     public void updateBackgroundForGroupState() {
1575         if (mIsSummaryWithChildren) {
1576             // Only when the group has finished expanding do we hide its background.
1577             mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked();
1578             mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
1579             List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
1580             for (int i = 0; i < children.size(); i++) {
1581                 children.get(i).updateBackgroundForGroupState();
1582             }
1583         } else if (isChildInGroup()) {
1584             final int childColor = getShowingLayout().getBackgroundColorForExpansionState();
1585             // Only show a background if the group is expanded OR if it is expanding / collapsing
1586             // and has a custom background color
1587             final boolean showBackground = isGroupExpanded()
1588                     || ((mNotificationParent.isGroupExpansionChanging()
1589                             || mNotificationParent.isUserLocked()) && childColor != 0);
1590             mShowNoBackground = !showBackground;
1591         } else {
1592             // Only children or parents ever need no background.
1593             mShowNoBackground = false;
1594         }
1595         updateOutline();
1596         updateBackground();
1597     }
1598 
1599     public int getPositionOfChild(ExpandableNotificationRow childRow) {
1600         if (mIsSummaryWithChildren) {
1601             return mChildrenContainer.getPositionInLinearLayout(childRow);
1602         }
1603         return 0;
1604     }
1605 
1606     public void setExpansionLogger(ExpansionLogger logger, String key) {
1607         mLogger = logger;
1608         mLoggingKey = key;
1609     }
1610 
1611     public void onExpandedByGesture(boolean userExpanded) {
1612         int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
1613         if (mGroupManager.isSummaryOfGroup(getStatusBarNotification())) {
1614             event = MetricsEvent.ACTION_NOTIFICATION_GROUP_GESTURE_EXPANDER;
1615         }
1616         MetricsLogger.action(mContext, event, userExpanded);
1617     }
1618 
1619     @Override
1620     public float getIncreasedPaddingAmount() {
1621         if (mIsSummaryWithChildren) {
1622             if (isGroupExpanded()) {
1623                 return 1.0f;
1624             } else if (isUserLocked()) {
1625                 return mChildrenContainer.getGroupExpandFraction();
1626             }
1627         }
1628         return 0.0f;
1629     }
1630 
1631     @Override
1632     protected boolean disallowSingleClick(MotionEvent event) {
1633         float x = event.getX();
1634         float y = event.getY();
1635         NotificationHeaderView header = getVisibleNotificationHeader();
1636         if (header != null) {
1637             return header.isInTouchRect(x - getTranslation(), y);
1638         }
1639         return super.disallowSingleClick(event);
1640     }
1641 
1642     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
1643         boolean nowExpanded = isExpanded();
1644         if (mIsSummaryWithChildren) {
1645             nowExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification);
1646         }
1647         if (wasExpanded != nowExpanded && mLogger != null) {
1648             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
1649         }
1650     }
1651 
1652     @Override
1653     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1654         super.onInitializeAccessibilityNodeInfoInternal(info);
1655         if (canViewBeDismissed()) {
1656             info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS);
1657         }
1658     }
1659 
1660     @Override
1661     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
1662         if (super.performAccessibilityActionInternal(action, arguments)) {
1663             return true;
1664         }
1665         switch (action) {
1666             case AccessibilityNodeInfo.ACTION_DISMISS:
1667                 NotificationStackScrollLayout.performDismiss(this, mGroupManager,
1668                         true /* fromAccessibility */);
1669                 return true;
1670         }
1671         return false;
1672     }
1673 
1674     public boolean shouldRefocusOnDismiss() {
1675         return mRefocusOnDismiss || isAccessibilityFocused();
1676     }
1677 
1678     public interface OnExpandClickListener {
1679         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
1680     }
1681 }
1682