blob: 5f01306f577464a6635a79ee625f2af5f91723fe [file] [log] [blame]
/*
* 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.graphics.drawable.RippleDrawable;
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);
}
};
private boolean mJustClicked;
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;
}
/**
* Mark whether this notification was just clicked, i.e. the user has just clicked this
* notification in this frame.
*/
public void setJustClicked(boolean justClicked) {
mJustClicked = justClicked;
}
/**
* @return true if this notification has been clicked in this frame, false otherwise
*/
public boolean wasJustClicked() {
return mJustClicked;
}
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) ;
}
}
}