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