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