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