/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; import android.view.animation.LinearInterpolator; import android.widget.ImageView; import com.android.systemui.R; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.PhoneStatusBar; import com.android.systemui.statusbar.stack.NotificationChildrenContainer; import com.android.systemui.statusbar.stack.StackScrollState; import com.android.systemui.statusbar.stack.StackStateAnimator; import com.android.systemui.statusbar.stack.StackViewState; import java.util.List; public class ExpandableNotificationRow extends ActivatableNotificationView { private static final int DEFAULT_DIVIDER_ALPHA = 0x29; private static final int COLORED_DIVIDER_ALPHA = 0x7B; private final LinearInterpolator mLinearInterpolator = new LinearInterpolator(); private int mRowMinHeight; /** Does this row contain layouts that can adapt to row expansion */ private boolean mExpandable; /** Has the user actively changed the expansion state of this row */ private boolean mHasUserChangedExpansion; /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ private boolean mUserExpanded; /** Is the user touching this row */ private boolean mUserLocked; /** Are we showing the "public" version */ private boolean mShowingPublic; private boolean mSensitive; private boolean mShowingPublicInitialized; private boolean mHideSensitiveForIntrinsicHeight; /** * Is this notification expanded by the system. The expansion state can be overridden by the * user expansion. */ private boolean mIsSystemExpanded; /** * Whether the notification expansion is disabled. This is the case on Keyguard. */ private boolean mExpansionDisabled; private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; private int mHeadsUpHeight; private View mVetoButton; private boolean mClearable; private ExpansionLogger mLogger; private String mLoggingKey; private boolean mWasReset; private NotificationGuts mGuts; private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; private View mExpandButton; private View mExpandButtonDivider; private ViewStub mExpandButtonStub; private ViewStub mChildrenContainerStub; private NotificationGroupManager mGroupManager; private View mExpandButtonContainer; private boolean mChildrenExpanded; private NotificationChildrenContainer mChildrenContainer; private ValueAnimator mChildExpandAnimator; private float mChildrenExpandProgress; private float mExpandButtonStart; private ViewStub mGutsStub; private boolean mHasExpandAction; private boolean mIsSystemChildExpanded; private boolean mIsPinned; private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { mGroupManager.setGroupExpanded(mStatusBarNotification, !mChildrenExpanded); } }; public NotificationContentView getPrivateLayout() { return mPrivateLayout; } public NotificationContentView getPublicLayout() { return mPublicLayout; } public void setIconAnimationRunning(boolean running) { setIconAnimationRunning(running, mPublicLayout); setIconAnimationRunning(running, mPrivateLayout); } private void setIconAnimationRunning(boolean running, NotificationContentView layout) { if (layout != null) { View contractedChild = layout.getContractedChild(); View expandedChild = layout.getExpandedChild(); View headsUpChild = layout.getHeadsUpChild(); setIconAnimationRunningForChild(running, contractedChild); setIconAnimationRunningForChild(running, expandedChild); setIconAnimationRunningForChild(running, headsUpChild); } } private void setIconAnimationRunningForChild(boolean running, View child) { if (child != null) { ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); setIconRunning(icon, running); ImageView rightIcon = (ImageView) child.findViewById( com.android.internal.R.id.right_icon); setIconRunning(rightIcon, running); } } private void setIconRunning(ImageView imageView, boolean running) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof AnimationDrawable) { AnimationDrawable animationDrawable = (AnimationDrawable) drawable; if (running) { animationDrawable.start(); } else { animationDrawable.stop(); } } else if (drawable instanceof AnimatedVectorDrawable) { AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; if (running) { animationDrawable.start(); } else { animationDrawable.stop(); } } } } public void setStatusBarNotification(StatusBarNotification statusBarNotification) { mStatusBarNotification = statusBarNotification; updateVetoButton(); updateExpandButton(); } public StatusBarNotification getStatusBarNotification() { return mStatusBarNotification; } public boolean isHeadsUp() { return mIsHeadsUp; } public void setHeadsUp(boolean isHeadsUp) { int intrinsicBefore = getIntrinsicHeight(); mIsHeadsUp = isHeadsUp; mPrivateLayout.setHeadsUp(isHeadsUp); if (intrinsicBefore != getIntrinsicHeight()) { notifyHeightChanged(false /* needsAnimation */); } } public void setGroupManager(NotificationGroupManager groupManager) { mGroupManager = groupManager; } public void addChildNotification(ExpandableNotificationRow row) { addChildNotification(row, -1); } /** * Add a child notification to this view. * * @param row the row to add * @param childIndex the index to add it at, if -1 it will be added at the end */ public void addChildNotification(ExpandableNotificationRow row, int childIndex) { if (mChildrenContainer == null) { mChildrenContainerStub.inflate(); } mChildrenContainer.addNotification(row, childIndex); } public void removeChildNotification(ExpandableNotificationRow row) { if (mChildrenContainer != null) { mChildrenContainer.removeNotification(row); } } @Override public boolean areChildrenExpanded() { return mChildrenExpanded; } public List<ExpandableNotificationRow> getNotificationChildren() { return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); } /** * Apply the order given in the list to the children. * * @param childOrder the new list order * @return whether the list order has changed */ public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); } public void getChildrenStates(StackScrollState resultState) { if (mChildrenExpanded) { StackViewState parentState = resultState.getViewStateForView(this); mChildrenContainer.getState(resultState, parentState); } } public void applyChildrenState(StackScrollState state) { if (mChildrenExpanded) { mChildrenContainer.applyState(state); } } public void prepareExpansionChanged(StackScrollState state) { if (mChildrenExpanded) { mChildrenContainer.prepareExpansionChanged(state); } } public void startChildAnimation(StackScrollState finalState, StackStateAnimator stateAnimator, boolean withDelays, long delay, long duration) { if (mChildrenExpanded) { mChildrenContainer.startAnimationToState(finalState, stateAnimator, withDelays, delay, duration); } } public ExpandableNotificationRow getViewAtPosition(float y) { if (!mChildrenExpanded) { return this; } else { ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); return view == null ? this : view; } } public NotificationGuts getGuts() { return mGuts; } protected int calculateContentHeightFromActualHeight(int actualHeight) { int realActualHeight = actualHeight; if (hasBottomDecor()) { realActualHeight -= getBottomDecorHeight(); } realActualHeight = Math.max(getMinHeight(), realActualHeight); return realActualHeight; } /** * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this * the notification will be rendered on top of the screen. * * @param pinned whether it is pinned */ public void setPinned(boolean pinned) { mIsPinned = pinned; } public boolean isPinned() { return mIsPinned; } public int getHeadsUpHeight() { return mHeadsUpHeight; } public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); } /** * Resets this view so it can be re-used for an updated notification. */ @Override public void reset() { super.reset(); mRowMinHeight = 0; final boolean wasExpanded = isExpanded(); mMaxViewHeight = 0; mExpandable = false; mHasUserChangedExpansion = false; mUserLocked = false; mShowingPublic = false; mSensitive = false; mShowingPublicInitialized = false; mIsSystemExpanded = false; mExpansionDisabled = false; mPublicLayout.reset(mIsHeadsUp); mPrivateLayout.reset(mIsHeadsUp); resetHeight(); logExpansionEvent(false, wasExpanded); } public void resetHeight() { if (mIsHeadsUp) { resetActualHeight(); } mMaxExpandHeight = 0; mHeadsUpHeight = 0; mWasReset = true; onHeightReset(); requestLayout(); } @Override protected boolean filterMotionEvent(MotionEvent event) { return mIsHeadsUp || super.filterMotionEvent(event); } @Override protected void onFinishInflate() { super.onFinishInflate(); mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { mGuts = (NotificationGuts) inflated; mGuts.setClipTopAmount(getClipTopAmount()); mGuts.setActualHeight(getActualHeight()); mGutsStub = null; } }); mExpandButtonStub = (ViewStub) findViewById(R.id.more_button_stub); mExpandButtonStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { mExpandButtonContainer = inflated; mExpandButton = inflated.findViewById(R.id.notification_expand_button); mExpandButtonDivider = inflated.findViewById(R.id.notification_expand_divider); mExpandButtonContainer.setOnClickListener(mExpandClickListener); } }); mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { mChildrenContainer = (NotificationChildrenContainer) inflated; mChildrenContainer.setCollapseClickListener(mExpandClickListener); updateChildrenVisibility(false); } }); mVetoButton = findViewById(R.id.veto); } public void inflateGuts() { if (mGuts == null) { mGutsStub.inflate(); } } private void updateChildrenVisibility(boolean animated) { if (mChildrenContainer == null) { return; } if (mChildExpandAnimator != null) { mChildExpandAnimator.cancel(); } float targetProgress = mChildrenExpanded ? 1.0f : 0.0f; if (animated) { if (mChildrenExpanded) { mChildrenContainer.setVisibility(VISIBLE); } mExpandButtonStart = mExpandButtonContainer.getTranslationY(); mChildExpandAnimator = ValueAnimator.ofFloat(mChildrenExpandProgress, targetProgress); mChildExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { setChildrenExpandProgress((float) animation.getAnimatedValue()); } }); mChildExpandAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mChildExpandAnimator = null; if (!mChildrenExpanded) { mChildrenContainer.setVisibility(INVISIBLE); } } }); mChildExpandAnimator.setInterpolator(mLinearInterpolator); mChildExpandAnimator.setDuration( StackStateAnimator.ANIMATION_DURATION_EXPAND_CLICKED); mChildExpandAnimator.start(); } else { setChildrenExpandProgress(targetProgress); mChildrenContainer.setVisibility(mChildrenExpanded ? VISIBLE : INVISIBLE); } } private void setChildrenExpandProgress(float progress) { mChildrenExpandProgress = progress; updateExpandButtonAppearance(); NotificationContentView showingLayout = getShowingLayout(); float alpha = 1.0f - mChildrenExpandProgress; alpha = PhoneStatusBar.ALPHA_OUT.getInterpolation(alpha); showingLayout.setAlpha(alpha); } @Override public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { if (super.onRequestSendAccessibilityEventInternal(child, event)) { // Add a record for the entire layout since its content is somehow small. // The event comes from a leaf view that is interacted with. AccessibilityEvent record = AccessibilityEvent.obtain(); onInitializeAccessibilityEvent(record); dispatchPopulateAccessibilityEvent(record); event.appendRecord(record); return true; } return false; } @Override public void setDark(boolean dark, boolean fade, long delay) { super.setDark(dark, fade, delay); final NotificationContentView showing = getShowingLayout(); if (showing != null) { showing.setDark(dark, fade, delay); } } public void setHeightRange(int rowMinHeight, int rowMaxHeight) { mRowMinHeight = rowMinHeight; mMaxViewHeight = rowMaxHeight; } public boolean isExpandable() { return mExpandable; } public void setExpandable(boolean expandable) { mExpandable = expandable; } /** * @return whether the user has changed the expansion state */ public boolean hasUserChangedExpansion() { return mHasUserChangedExpansion; } public boolean isUserExpanded() { return mUserExpanded; } /** * Set this notification to be expanded by the user * * @param userExpanded whether the user wants this notification to be expanded */ public void setUserExpanded(boolean userExpanded) { if (userExpanded && !mExpandable) return; final boolean wasExpanded = isExpanded(); mHasUserChangedExpansion = true; mUserExpanded = userExpanded; logExpansionEvent(true, wasExpanded); } public void resetUserExpansion() { mHasUserChangedExpansion = false; mUserExpanded = false; } public boolean isUserLocked() { return mUserLocked; } public void setUserLocked(boolean userLocked) { mUserLocked = userLocked; } /** * @return has the system set this notification to be expanded */ public boolean isSystemExpanded() { return mIsSystemExpanded; } /** * Set this notification to be expanded by the system. * * @param expand whether the system wants this notification to be expanded. */ public void setSystemExpanded(boolean expand) { if (expand != mIsSystemExpanded) { final boolean wasExpanded = isExpanded(); mIsSystemExpanded = expand; notifyHeightChanged(false /* needsAnimation */); logExpansionEvent(false, wasExpanded); } } /** * @param expansionDisabled whether to prevent notification expansion */ public void setExpansionDisabled(boolean expansionDisabled) { if (expansionDisabled != mExpansionDisabled) { final boolean wasExpanded = isExpanded(); mExpansionDisabled = expansionDisabled; logExpansionEvent(false, wasExpanded); if (wasExpanded != isExpanded()) { notifyHeightChanged(false /* needsAnimation */); } } } /** * @return Can the underlying notification be cleared? */ public boolean isClearable() { return mStatusBarNotification != null && mStatusBarNotification.isClearable(); } /** * Apply an expansion state to the layout. */ public void applyExpansionToLayout() { boolean expand = isExpanded(); if (expand && mExpandable) { setContentHeight(mMaxExpandHeight); } else { setContentHeight(mRowMinHeight); } } @Override public int getIntrinsicHeight() { if (isUserLocked()) { return getActualHeight(); } boolean inExpansionState = isExpanded(); int maxContentHeight; if (mSensitive && mHideSensitiveForIntrinsicHeight) { return mRowMinHeight; } else if (mIsHeadsUp) { if (inExpansionState) { maxContentHeight = Math.max(mMaxExpandHeight, mHeadsUpHeight); } else { maxContentHeight = Math.max(mRowMinHeight, mHeadsUpHeight); } } else if ((!inExpansionState && !mChildrenExpanded)) { maxContentHeight = mRowMinHeight; } else if (mChildrenExpanded) { maxContentHeight = mChildrenContainer.getIntrinsicHeight(); } else { maxContentHeight = getMaxExpandHeight(); } return maxContentHeight + getBottomDecorHeight(); } @Override protected boolean hasBottomDecor() { return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp && mGroupManager.hasGroupChildren(mStatusBarNotification); } @Override protected boolean canHaveBottomDecor() { return BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && !mIsHeadsUp; } /** * Check whether the view state is currently expanded. This is given by the system in {@link * #setSystemExpanded(boolean)} and can be overridden by user expansion or * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this * view can differ from this state, if layout params are modified from outside. * * @return whether the view state is currently expanded. */ private boolean isExpanded() { return !mExpansionDisabled && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) || isUserExpanded()); } private boolean isSystemChildExpanded() { return mIsSystemChildExpanded; } public void setSystemChildExpanded(boolean expanded) { mIsSystemChildExpanded = expanded; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset; updateMaxHeights(); if (updateExpandHeight) { applyExpansionToLayout(); } mWasReset = false; } @Override protected boolean isChildInvisible(View child) { // We don't want to layout the ChildrenContainer if this is a heads-up view, otherwise the // view will get too high and the shadows will be off. boolean isInvisibleChildContainer = child == mChildrenContainer && mIsHeadsUp; return super.isChildInvisible(child) || isInvisibleChildContainer; } private void updateMaxHeights() { int intrinsicBefore = getIntrinsicHeight(); View expandedChild = mPrivateLayout.getExpandedChild(); if (expandedChild == null) { expandedChild = mPrivateLayout.getContractedChild(); } mMaxExpandHeight = expandedChild.getHeight(); View headsUpChild = mPrivateLayout.getHeadsUpChild(); if (headsUpChild == null) { headsUpChild = mPrivateLayout.getContractedChild(); } mHeadsUpHeight = headsUpChild.getHeight(); if (intrinsicBefore != getIntrinsicHeight()) { notifyHeightChanged(false /* needsAnimation */); } } public void setSensitive(boolean sensitive) { mSensitive = sensitive; } public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { mHideSensitiveForIntrinsicHeight = hideSensitive; } public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration) { boolean oldShowingPublic = mShowingPublic; mShowingPublic = mSensitive && hideSensitive; if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { return; } // bail out if no public version if (mPublicLayout.getChildCount() == 0) return; if (!animated) { mPublicLayout.animate().cancel(); mPrivateLayout.animate().cancel(); mPublicLayout.setAlpha(1f); mPrivateLayout.setAlpha(1f); mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE); } else { animateShowingPublic(delay, duration); } updateVetoButton(); mShowingPublicInitialized = true; } private void animateShowingPublic(long delay, long duration) { final View source = mShowingPublic ? mPrivateLayout : mPublicLayout; View target = mShowingPublic ? mPublicLayout : mPrivateLayout; source.setVisibility(View.VISIBLE); target.setVisibility(View.VISIBLE); target.setAlpha(0f); source.animate().cancel(); target.animate().cancel(); source.animate() .alpha(0f) .setStartDelay(delay) .setDuration(duration) .withEndAction(new Runnable() { @Override public void run() { source.setVisibility(View.INVISIBLE); } }); target.animate() .alpha(1f) .setStartDelay(delay) .setDuration(duration); } private void updateVetoButton() { // public versions cannot be dismissed mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE); } public void setChildrenExpanded(boolean expanded, boolean animate) { mChildrenExpanded = expanded; updateChildrenVisibility(animate); } public void updateExpandButton() { boolean hasExpand = hasBottomDecor(); if (hasExpand != mHasExpandAction) { if (hasExpand) { if (mExpandButtonContainer == null) { mExpandButtonStub.inflate(); } mExpandButtonContainer.setVisibility(View.VISIBLE); updateExpandButtonAppearance(); updateExpandButtonColor(); } else if (mExpandButtonContainer != null) { mExpandButtonContainer.setVisibility(View.GONE); } notifyHeightChanged(true /* needsAnimation */); } mHasExpandAction = hasExpand; } private void updateExpandButtonAppearance() { if (mExpandButtonContainer == null) { return; } float expandButtonAlpha = 0.0f; float expandButtonTranslation = 0.0f; float containerTranslation = 0.0f; int minHeight = getMinHeight(); if (!mChildrenExpanded || mChildExpandAnimator != null) { int expandActionHeight = getBottomDecorHeight(); int translationY = getActualHeight() - expandActionHeight; if (translationY > minHeight) { containerTranslation = translationY; expandButtonAlpha = 1.0f; expandButtonTranslation = 0.0f; } else { containerTranslation = minHeight; float progress = expandActionHeight != 0 ? (minHeight - translationY) / (float) expandActionHeight : 1.0f; expandButtonTranslation = -progress * expandActionHeight * 0.7f; float alphaProgress = Math.min(progress / 0.7f, 1.0f); alphaProgress = PhoneStatusBar.ALPHA_OUT.getInterpolation(alphaProgress); expandButtonAlpha = 1.0f - alphaProgress; } } if (mChildExpandAnimator != null || mChildrenExpanded) { expandButtonAlpha = (1.0f - mChildrenExpandProgress) * expandButtonAlpha; expandButtonTranslation = (1.0f - mChildrenExpandProgress) * expandButtonTranslation; float newTranslation = -getBottomDecorHeight(); // We don't want to take the actual height of the view as this is already // interpolated by a custom interpolator leading to a confusing animation. We want // to have a stable end value to interpolate in between float collapsedHeight = !mChildrenExpanded ? Math.max(StackStateAnimator.getFinalActualHeight(this) - getBottomDecorHeight(), minHeight) : mExpandButtonStart; float translationProgress = mFastOutSlowInInterpolator.getInterpolation( mChildrenExpandProgress); containerTranslation = (1.0f - translationProgress) * collapsedHeight + translationProgress * newTranslation; } mExpandButton.setAlpha(expandButtonAlpha); mExpandButtonDivider.setAlpha(expandButtonAlpha); mExpandButton.setTranslationY(expandButtonTranslation); mExpandButtonContainer.setTranslationY(containerTranslation); NotificationContentView showingLayout = getShowingLayout(); float layoutTranslation = mExpandButtonContainer.getTranslationY() - showingLayout.getContentHeight(); layoutTranslation = Math.min(layoutTranslation, 0); if (!mChildrenExpanded && mChildExpandAnimator == null) { // Needed for the DragDownHelper in order not to jump there, as the position // can be negative for a short time. layoutTranslation = 0; } showingLayout.setTranslationY(layoutTranslation); if (mChildrenContainer != null) { mChildrenContainer.setTranslationY( mExpandButtonContainer.getTranslationY() + getBottomDecorHeight()); } } private void updateExpandButtonColor() { // TODO: This needs some more baking, currently only the divider is colored according to // the tint, but legacy black doesn't work yet perfectly for the button etc. int color = getRippleColor(); if (color == mNormalRippleColor) { color = 0; } if (mExpandButtonDivider != null) { applyTint(mExpandButtonDivider, color); } if (mChildrenContainer != null) { mChildrenContainer.setTintColor(color); } } public static void applyTint(View v, int color) { int alpha; if (color != 0) { alpha = COLORED_DIVIDER_ALPHA; } else { color = 0xff000000; alpha = DEFAULT_DIVIDER_ALPHA; } if (v.getBackground() instanceof ColorDrawable) { ColorDrawable background = (ColorDrawable) v.getBackground(); background.mutate(); background.setColor(color); background.setAlpha(alpha); } } public int getMaxExpandHeight() { return mMaxExpandHeight; } @Override public boolean isContentExpandable() { NotificationContentView showingLayout = getShowingLayout(); return showingLayout.isContentExpandable(); } @Override protected View getContentView() { return getShowingLayout(); } @Override public void setActualHeight(int height, boolean notifyListeners) { super.setActualHeight(height, notifyListeners); int contentHeight = calculateContentHeightFromActualHeight(height); mPrivateLayout.setContentHeight(contentHeight); mPublicLayout.setContentHeight(contentHeight); if (mGuts != null) { mGuts.setActualHeight(height); } invalidate(); updateExpandButtonAppearance(); } @Override public int getMaxContentHeight() { NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMaxHeight(); } @Override public int getMinHeight() { NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMinHeight(); } @Override public void setClipTopAmount(int clipTopAmount) { super.setClipTopAmount(clipTopAmount); mPrivateLayout.setClipTopAmount(clipTopAmount); mPublicLayout.setClipTopAmount(clipTopAmount); if (mGuts != null) { mGuts.setClipTopAmount(clipTopAmount); } } public void notifyContentUpdated() { mPublicLayout.notifyContentUpdated(); mPrivateLayout.notifyContentUpdated(); } public boolean isMaxExpandHeightInitialized() { return mMaxExpandHeight != 0; } private NotificationContentView getShowingLayout() { return mShowingPublic ? mPublicLayout : mPrivateLayout; } @Override public void setShowingLegacyBackground(boolean showing) { super.setShowingLegacyBackground(showing); mPrivateLayout.setShowingLegacyBackground(showing); mPublicLayout.setShowingLegacyBackground(showing); } public void setExpansionLogger(ExpansionLogger logger, String key) { mLogger = logger; mLoggingKey = key; } private void logExpansionEvent(boolean userAction, boolean wasExpanded) { final boolean nowExpanded = isExpanded(); if (wasExpanded != nowExpanded && mLogger != null) { mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ; } } }