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