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