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