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