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.ObjectAnimator;
22import android.animation.ValueAnimator.AnimatorUpdateListener;
23import android.annotation.Nullable;
24import android.content.Context;
25import android.graphics.drawable.AnimatedVectorDrawable;
26import android.graphics.drawable.AnimationDrawable;
27import android.graphics.drawable.ColorDrawable;
28import android.graphics.drawable.Drawable;
29import android.os.Build;
30import android.os.Bundle;
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.view.accessibility.AccessibilityNodeInfo;
42import android.widget.Chronometer;
43import android.widget.ImageView;
44
45import com.android.internal.logging.MetricsLogger;
46import com.android.internal.logging.MetricsProto.MetricsEvent;
47import com.android.internal.util.NotificationColorUtil;
48import com.android.systemui.R;
49import com.android.systemui.classifier.FalsingManager;
50import com.android.systemui.statusbar.notification.HybridNotificationView;
51import com.android.systemui.statusbar.phone.NotificationGroupManager;
52import com.android.systemui.statusbar.policy.HeadsUpManager;
53import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
54import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
55import com.android.systemui.statusbar.stack.StackScrollState;
56import com.android.systemui.statusbar.stack.StackStateAnimator;
57import com.android.systemui.statusbar.stack.StackViewState;
58
59import java.util.ArrayList;
60import java.util.List;
61
62public 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
193    public boolean isGroupExpansionChanging() {
194        if (isChildInGroup()) {
195            return mNotificationParent.isGroupExpansionChanging();
196        }
197        return mGroupExpansionChanging;
198    }
199
200    public void setGroupExpansionChanging(boolean changing) {
201        mGroupExpansionChanging = changing;
202    }
203
204    @Override
205    public void setActualHeightAnimating(boolean animating) {
206        if (mPrivateLayout != null) {
207            mPrivateLayout.setContentHeightAnimating(animating);
208        }
209    }
210
211    public NotificationContentView getPrivateLayout() {
212        return mPrivateLayout;
213    }
214
215    public NotificationContentView getPublicLayout() {
216        return mPublicLayout;
217    }
218
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
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
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
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
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
300    private void updateLimits() {
301        updateLimitsForView(mPrivateLayout);
302        updateLimitsForView(mPublicLayout);
303    }
304
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