blob: b8f8cb2b5bc91bbe97cf3b385d26450c8eb04640 [file] [log] [blame]
/*
* Copyright (C) 2015 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.stack;
import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.ColorDrawable;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.ViewInvertHelper;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationHeaderUtil;
import com.android.systemui.statusbar.notification.HybridGroupManager;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.util.ArrayList;
import java.util.List;
/**
* A container containing child notifications
*/
public class NotificationChildrenContainer extends ViewGroup {
private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
private final List<View> mDividers = new ArrayList<>();
private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
private final HybridGroupManager mHybridGroupManager;
private int mChildPadding;
private int mDividerHeight;
private int mMaxNotificationHeight;
private int mNotificationHeaderMargin;
private int mNotificatonTopPadding;
private float mCollapsedBottompadding;
private ViewInvertHelper mOverflowInvertHelper;
private boolean mChildrenExpanded;
private ExpandableNotificationRow mNotificationParent;
private TextView mOverflowNumber;
private ViewState mGroupOverFlowState;
private int mRealHeight;
private boolean mUserLocked;
private int mActualHeight;
private boolean mNeverAppliedGroupState;
private int mHeaderHeight;
private NotificationHeaderView mNotificationHeader;
private NotificationViewWrapper mNotificationHeaderWrapper;
private NotificationHeaderUtil mHeaderUtil;
private ViewState mHeaderViewState;
private int mClipBottomAmount;
public NotificationChildrenContainer(Context context) {
this(context, null);
}
public NotificationChildrenContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initDimens();
mHybridGroupManager = new HybridGroupManager(getContext(), this);
}
private void initDimens() {
mChildPadding = getResources().getDimensionPixelSize(
R.dimen.notification_children_padding);
mDividerHeight = Math.max(1, getResources().getDimensionPixelSize(
R.dimen.notification_divider_height));
mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.notification_header_height);
mMaxNotificationHeight = getResources().getDimensionPixelSize(
R.dimen.notification_max_height);
mNotificationHeaderMargin = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_top);
mNotificatonTopPadding = getResources().getDimensionPixelSize(
R.dimen.notification_children_container_top_padding);
mCollapsedBottompadding = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_bottom);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
for (int i = 0; i < childCount; i++) {
View child = mChildren.get(i);
// We need to layout all children even the GONE ones, such that the heights are
// calculated correctly as they are used to calculate how many we can fit on the screen
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
}
if (mOverflowNumber != null) {
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
int left = (isRtl ? 0 : getWidth() - mOverflowNumber.getMeasuredWidth());
int right = left + mOverflowNumber.getMeasuredWidth();
mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight());
}
if (mNotificationHeader != null) {
mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
mNotificationHeader.getMeasuredHeight());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int ownMaxHeight = mMaxNotificationHeight;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
int size = MeasureSpec.getSize(heightMeasureSpec);
if (hasFixedHeight || isHeightLimited) {
ownMaxHeight = Math.min(ownMaxHeight, size);
}
int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (mOverflowNumber != null) {
mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
newHeightSpec);
}
int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
int height = mNotificationHeaderMargin + mNotificatonTopPadding;
int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
// We need to measure all children even the GONE ones, such that the heights are
// calculated correctly as they are used to calculate how many we can fit on the screen.
boolean isOverflow = i == overflowIndex;
child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null
? mOverflowNumber.getMeasuredWidth()
: 0);
child.measure(widthMeasureSpec, newHeightSpec);
// layout the divider
View divider = mDividers.get(i);
divider.measure(widthMeasureSpec, dividerHeightSpec);
if (child.getVisibility() != GONE) {
height += child.getMeasuredHeight() + mDividerHeight;
}
}
mRealHeight = height;
if (heightMode != MeasureSpec.UNSPECIFIED) {
height = Math.min(height, size);
}
if (mNotificationHeader != null) {
int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
}
setMeasuredDimension(width, height);
}
@Override
public boolean hasOverlappingRendering() {
return false;
}
@Override
public boolean pointInView(float localX, float localY, float slop) {
return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
localY < (mRealHeight + slop);
}
/**
* 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 addNotification(ExpandableNotificationRow row, int childIndex) {
int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
mChildren.add(newIndex, row);
addView(row);
row.setUserLocked(mUserLocked);
View divider = inflateDivider();
addView(divider);
mDividers.add(newIndex, divider);
updateGroupOverflow();
row.setIconTransformationAmount(0, false /* isLastChild */);
}
public void removeNotification(ExpandableNotificationRow row) {
int childIndex = mChildren.indexOf(row);
mChildren.remove(row);
removeView(row);
final View divider = mDividers.remove(childIndex);
removeView(divider);
getOverlay().add(divider);
CrossFadeHelper.fadeOut(divider, new Runnable() {
@Override
public void run() {
getOverlay().remove(divider);
}
});
row.setSystemChildExpanded(false);
row.setUserLocked(false);
updateGroupOverflow();
if (!row.isRemoved()) {
mHeaderUtil.restoreNotificationHeader(row);
}
}
/**
* @return The number of notification children in the container.
*/
public int getNotificationChildCount() {
return mChildren.size();
}
public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) {
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
mNotificationParent.getStatusBarNotification().getNotification());
final RemoteViews header = builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
final View expandButton = mNotificationHeader.findViewById(
com.android.internal.R.id.expand_button);
expandButton.setVisibility(VISIBLE);
mNotificationHeader.setOnClickListener(listener);
mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
mNotificationHeader, mNotificationParent);
addView(mNotificationHeader, 0);
invalidate();
} else {
header.reapply(getContext(), mNotificationHeader);
mNotificationHeaderWrapper.notifyContentUpdated(notification);
}
updateChildrenHeaderAppearance();
}
public void updateChildrenHeaderAppearance() {
mHeaderUtil.updateChildrenHeaderAppearance();
}
public void updateGroupOverflow() {
int childCount = mChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
if (childCount > maxAllowedVisibleChildren) {
mOverflowNumber = mHybridGroupManager.bindOverflowNumber(
mOverflowNumber, childCount - maxAllowedVisibleChildren);
if (mOverflowInvertHelper == null) {
mOverflowInvertHelper = new ViewInvertHelper(mOverflowNumber,
NotificationPanelView.DOZE_ANIMATION_DURATION);
}
if (mGroupOverFlowState == null) {
mGroupOverFlowState = new ViewState();
mNeverAppliedGroupState = true;
}
} else if (mOverflowNumber != null) {
removeView(mOverflowNumber);
if (isShown()) {
final View removedOverflowNumber = mOverflowNumber;
addTransientView(removedOverflowNumber, getTransientViewCount());
CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
@Override
public void run() {
removeTransientView(removedOverflowNumber);
}
});
}
mOverflowNumber = null;
mOverflowInvertHelper = null;
mGroupOverFlowState = null;
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateGroupOverflow();
}
private View inflateDivider() {
return LayoutInflater.from(mContext).inflate(
R.layout.notification_children_divider, this, false);
}
public List<ExpandableNotificationRow> getNotificationChildren() {
return mChildren;
}
/**
* 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) {
if (childOrder == null) {
return false;
}
boolean result = false;
for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableNotificationRow desiredChild = childOrder.get(i);
if (child != desiredChild) {
mChildren.remove(desiredChild);
mChildren.add(i, desiredChild);
result = true;
}
}
updateExpansionStates();
return result;
}
private void updateExpansionStates() {
if (mChildrenExpanded || mUserLocked) {
// we don't modify it the group is expanded or if we are expanding it
return;
}
int size = mChildren.size();
for (int i = 0; i < size; i++) {
ExpandableNotificationRow child = mChildren.get(i);
child.setSystemChildExpanded(i == 0 && size == 1);
}
}
/**
*
* @return the intrinsic size of this children container, i.e the natural fully expanded state
*/
public int getIntrinsicHeight() {
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
return getIntrinsicHeight(maxAllowedVisibleChildren);
}
/**
* @return the intrinsic height with a number of children given
* in @param maxAllowedVisibleChildren
*/
private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
int intrinsicHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
int childCount = mChildren.size();
boolean firstChild = true;
float expandFactor = 0;
if (mUserLocked) {
expandFactor = getGroupExpandFraction();
}
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
if (!firstChild) {
if (mUserLocked) {
intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
expandFactor);
} else {
intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding;
}
} else {
if (mUserLocked) {
intrinsicHeight += NotificationUtils.interpolate(
0,
mNotificatonTopPadding + mDividerHeight,
expandFactor);
} else {
intrinsicHeight += mChildrenExpanded
? mNotificatonTopPadding + mDividerHeight
: 0;
}
firstChild = false;
}
ExpandableNotificationRow child = mChildren.get(i);
intrinsicHeight += child.getIntrinsicHeight();
visibleChildren++;
}
if (mUserLocked) {
intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
expandFactor);
} else if (!mChildrenExpanded) {
intrinsicHeight += mCollapsedBottompadding;
}
return intrinsicHeight;
}
/**
* Update the state of all its children based on a linear layout algorithm.
*
* @param resultState the state to update
* @param parentState the state of the parent
*/
public void getState(StackScrollState resultState, ExpandableViewState parentState) {
int childCount = mChildren.size();
int yPosition = mNotificationHeaderMargin;
boolean firstChild = true;
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
int lastVisibleIndex = maxAllowedVisibleChildren - 1;
int firstOverflowIndex = lastVisibleIndex + 1;
float expandFactor = 0;
if (mUserLocked) {
expandFactor = getGroupExpandFraction();
firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
}
boolean childrenExpanded = !mNotificationParent.isGroupExpansionChanging()
&& mChildrenExpanded;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (!firstChild) {
if (mUserLocked) {
yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
expandFactor);
} else {
yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
}
} else {
if (mUserLocked) {
yPosition += NotificationUtils.interpolate(
0,
mNotificatonTopPadding + mDividerHeight,
expandFactor);
} else {
yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
}
firstChild = false;
}
ExpandableViewState childState = resultState.getViewStateForView(child);
int intrinsicHeight = child.getIntrinsicHeight();
childState.height = intrinsicHeight;
childState.yTranslation = yPosition;
childState.hidden = false;
// When the group is expanded, the children cast the shadows rather than the parent
// so use the parent's elevation here.
childState.zTranslation = childrenExpanded
? mNotificationParent.getTranslationZ()
: 0;
childState.dimmed = parentState.dimmed;
childState.dark = parentState.dark;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
childState.alpha = 0;
if (i < firstOverflowIndex) {
childState.alpha = 1;
} else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
}
childState.location = parentState.location;
yPosition += intrinsicHeight;
}
if (mOverflowNumber != null) {
ExpandableNotificationRow overflowView = mChildren.get(Math.min(
getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1);
mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView));
if (!mChildrenExpanded) {
if (mUserLocked) {
HybridNotificationView singleLineView = overflowView.getSingleLineView();
View mirrorView = singleLineView.getTextView();
if (mirrorView.getVisibility() == GONE) {
mirrorView = singleLineView.getTitleView();
}
if (mirrorView.getVisibility() == GONE) {
mirrorView = singleLineView;
}
mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
mirrorView, overflowView);
mGroupOverFlowState.alpha = mirrorView.getAlpha();
}
} else {
mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
mGroupOverFlowState.alpha = 0.0f;
}
}
if (mNotificationHeader != null) {
if (mHeaderViewState == null) {
mHeaderViewState = new ViewState();
}
mHeaderViewState.initFrom(mNotificationHeader);
mHeaderViewState.zTranslation = childrenExpanded
? mNotificationParent.getTranslationZ()
: 0;
}
}
/**
* When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
* height, children in the group after this are gone.
*
* @param child the child who's height to adjust.
* @param parentHeight the height of the parent.
* @param childState the state to update.
* @param yPosition the yPosition of the view.
* @return true if children after this one should be hidden.
*/
private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
int parentHeight, ExpandableViewState childState, int yPosition) {
final int top = yPosition + child.getClipTopAmount();
final int intrinsicHeight = child.getIntrinsicHeight();
final int bottom = top + intrinsicHeight;
int newHeight = intrinsicHeight;
if (bottom >= parentHeight) {
// Child is either clipped or gone
newHeight = Math.max((parentHeight - top), 0);
}
childState.hidden = newHeight == 0;
childState.height = newHeight;
return childState.height != intrinsicHeight && !childState.hidden;
}
private int getMaxAllowedVisibleChildren() {
return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
}
private int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) {
return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
}
if (!mNotificationParent.isOnKeyguard()
&& (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp())) {
return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
}
return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
}
public void applyState(StackScrollState state) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
float expandFraction = 0.0f;
if (mUserLocked) {
expandFraction = getGroupExpandFraction();
}
final boolean dividersVisible = mUserLocked
|| mNotificationParent.isGroupExpansionChanging();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
viewState.applyToView(child);
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
if (mUserLocked && viewState.alpha != 0) {
alpha = NotificationUtils.interpolate(0, 0.5f,
Math.min(viewState.alpha, expandFraction));
}
tmpState.hidden = !dividersVisible;
tmpState.alpha = alpha;
tmpState.applyToView(divider);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
if (mGroupOverFlowState != null) {
mGroupOverFlowState.applyToView(mOverflowNumber);
mNeverAppliedGroupState = false;
}
if (mHeaderViewState != null) {
mHeaderViewState.applyToView(mNotificationHeader);
}
updateChildrenClipping();
}
private void updateChildrenClipping() {
int childCount = mChildren.size();
int layoutEnd = mNotificationParent.getActualHeight() - mClipBottomAmount;
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
if (child.getVisibility() == GONE) {
continue;
}
float childTop = child.getTranslationY();
float childBottom = childTop + child.getActualHeight();
boolean visible = true;
int clipBottomAmount = 0;
if (childTop > layoutEnd) {
visible = false;
} else if (childBottom > layoutEnd) {
clipBottomAmount = (int) (childBottom - layoutEnd);
}
boolean isVisible = child.getVisibility() == VISIBLE;
if (visible != isVisible) {
child.setVisibility(visible ? VISIBLE : INVISIBLE);
}
child.setClipBottomAmount(clipBottomAmount);
}
}
/**
* This is called when the children expansion has changed and positions the children properly
* for an appear animation.
*
* @param state the new state we animate to
*/
public void prepareExpansionChanged(StackScrollState state) {
// TODO: do something that makes sense, like placing the invisible views correctly
return;
}
public void startAnimationToState(StackScrollState state, AnimationProperties properties) {
int childCount = mChildren.size();
ViewState tmpState = new ViewState();
float expandFraction = getGroupExpandFraction();
final boolean dividersVisible = mUserLocked
|| mNotificationParent.isGroupExpansionChanging();
for (int i = childCount - 1; i >= 0; i--) {
ExpandableNotificationRow child = mChildren.get(i);
ExpandableViewState viewState = state.getViewStateForView(child);
viewState.animateTo(child, properties);
// layout the divider
View divider = mDividers.get(i);
tmpState.initFrom(divider);
tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
if (mUserLocked && viewState.alpha != 0) {
alpha = NotificationUtils.interpolate(0, 0.5f,
Math.min(viewState.alpha, expandFraction));
}
tmpState.hidden = !dividersVisible;
tmpState.alpha = alpha;
tmpState.animateTo(divider, properties);
// There is no fake shadow to be drawn on the children
child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
}
if (mOverflowNumber != null) {
if (mNeverAppliedGroupState) {
float alpha = mGroupOverFlowState.alpha;
mGroupOverFlowState.alpha = 0;
mGroupOverFlowState.applyToView(mOverflowNumber);
mGroupOverFlowState.alpha = alpha;
mNeverAppliedGroupState = false;
}
mGroupOverFlowState.animateTo(mOverflowNumber, properties);
}
if (mNotificationHeader != null) {
mHeaderViewState.applyToView(mNotificationHeader);
}
updateChildrenClipping();
}
public ExpandableNotificationRow getViewAtPosition(float y) {
// find the view under the pointer, accounting for GONE views
final int count = mChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
float childTop = slidingChild.getTranslationY();
float top = childTop + slidingChild.getClipTopAmount();
float bottom = childTop + slidingChild.getActualHeight();
if (y >= top && y <= bottom) {
return slidingChild;
}
}
return null;
}
public void setChildrenExpanded(boolean childrenExpanded) {
mChildrenExpanded = childrenExpanded;
updateExpansionStates();
if (mNotificationHeader != null) {
mNotificationHeader.setExpanded(childrenExpanded);
}
final int count = mChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
ExpandableNotificationRow child = mChildren.get(childIdx);
child.setChildrenExpanded(childrenExpanded, false);
}
}
public void setNotificationParent(ExpandableNotificationRow parent) {
mNotificationParent = parent;
mHeaderUtil = new NotificationHeaderUtil(mNotificationParent);
}
public ExpandableNotificationRow getNotificationParent() {
return mNotificationParent;
}
public NotificationHeaderView getHeaderView() {
return mNotificationHeader;
}
public void updateHeaderVisibility(int visiblity) {
if (mNotificationHeader != null) {
mNotificationHeader.setVisibility(visiblity);
}
}
/**
* Called when a groups expansion changes to adjust the background of the header view.
*
* @param expanded whether the group is expanded.
*/
public void updateHeaderForExpansion(boolean expanded) {
if (mNotificationHeader != null) {
if (expanded) {
ColorDrawable cd = new ColorDrawable();
cd.setColor(mNotificationParent.calculateBgColor());
mNotificationHeader.setHeaderBackgroundDrawable(cd);
} else {
mNotificationHeader.setHeaderBackgroundDrawable(null);
}
}
}
public int getMaxContentHeight() {
int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
int visibleChildren = 0;
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
break;
}
ExpandableNotificationRow child = mChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
maxContentHeight += childHeight;
visibleChildren++;
}
if (visibleChildren > 0) {
maxContentHeight += visibleChildren * mDividerHeight;
}
return maxContentHeight;
}
public void setActualHeight(int actualHeight) {
if (!mUserLocked) {
return;
}
mActualHeight = actualHeight;
float fraction = getGroupExpandFraction();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
if (i < maxAllowedVisibleChildren) {
float singleLineHeight = child.getShowingLayout().getMinHeight(
false /* likeGroupExpanded */);
child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
childHeight, fraction), false);
} else {
child.setActualHeight((int) childHeight, false);
}
}
}
public float getGroupExpandFraction() {
int visibleChildrenExpandedHeight = getVisibleChildrenExpandHeight();
int minExpandHeight = getCollapsedHeight();
float factor = (mActualHeight - minExpandHeight)
/ (float) (visibleChildrenExpandedHeight - minExpandHeight);
return Math.max(0.0f, Math.min(1.0f, factor));
}
private int getVisibleChildrenExpandHeight() {
int intrinsicHeight = mNotificationHeaderMargin + mNotificatonTopPadding + mDividerHeight;
int visibleChildren = 0;
int childCount = mChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
ExpandableNotificationRow child = mChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
intrinsicHeight += childHeight;
visibleChildren++;
}
return intrinsicHeight;
}
public int getMinHeight() {
return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED);
}
public int getCollapsedHeight() {
return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */));
}
private int getMinHeight(int maxAllowedVisibleChildren) {
int minExpandHeight = mNotificationHeaderMargin;
int visibleChildren = 0;
boolean firstChild = true;
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
if (!firstChild) {
minExpandHeight += mChildPadding;
} else {
firstChild = false;
}
ExpandableNotificationRow child = mChildren.get(i);
minExpandHeight += child.getSingleLineView().getHeight();
visibleChildren++;
}
minExpandHeight += mCollapsedBottompadding;
return minExpandHeight;
}
public void setDark(boolean dark, boolean fade, long delay) {
if (mOverflowNumber != null) {
mOverflowInvertHelper.setInverted(dark, fade, delay);
}
mNotificationHeaderWrapper.setDark(dark, fade, delay);
}
public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
removeView(mNotificationHeader);
mNotificationHeader = null;
recreateNotificationHeader(listener, notification);
initDimens();
for (int i = 0; i < mDividers.size(); i++) {
View prevDivider = mDividers.get(i);
int index = indexOfChild(prevDivider);
removeView(prevDivider);
View divider = inflateDivider();
addView(divider, index);
mDividers.set(i, divider);
}
removeView(mOverflowNumber);
mOverflowNumber = null;
mOverflowInvertHelper = null;
mGroupOverFlowState = null;
updateGroupOverflow();
}
public void setUserLocked(boolean userLocked) {
mUserLocked = userLocked;
int childCount = mChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableNotificationRow child = mChildren.get(i);
child.setUserLocked(userLocked);
}
}
public void onNotificationUpdated() {
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
mNotificationParent.getNotificationColor());
}
public int getPositionInLinearLayout(View childInGroup) {
int position = mNotificationHeaderMargin + mNotificatonTopPadding;
for (int i = 0; i < mChildren.size(); i++) {
ExpandableNotificationRow child = mChildren.get(i);
boolean notGone = child.getVisibility() != View.GONE;
if (notGone) {
position += mDividerHeight;
}
if (child == childInGroup) {
return position;
}
if (notGone) {
position += child.getIntrinsicHeight();
}
}
return 0;
}
public void setIconsVisible(boolean iconsVisible) {
if (mNotificationHeaderWrapper != null) {
NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
if (header != null) {
header.getIcon().setForceHidden(!iconsVisible);
}
}
}
public void setClipBottomAmount(int clipBottomAmount) {
mClipBottomAmount = clipBottomAmount;
updateChildrenClipping();
}
}