NotificationChildrenContainer.java revision a3d3b91a6eb1db65b52f12442973a1820754c8c0
1/* 2 * Copyright (C) 2015 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.stack; 18 19import android.content.Context; 20import android.content.res.Configuration; 21import android.util.AttributeSet; 22import android.view.LayoutInflater; 23import android.view.View; 24import android.view.ViewGroup; 25 26import com.android.systemui.R; 27import com.android.systemui.ViewInvertHelper; 28import com.android.systemui.statusbar.CrossFadeHelper; 29import com.android.systemui.statusbar.ExpandableNotificationRow; 30import com.android.systemui.statusbar.notification.HybridNotificationView; 31import com.android.systemui.statusbar.notification.HybridNotificationViewManager; 32import com.android.systemui.statusbar.phone.NotificationPanelView; 33 34import java.util.ArrayList; 35import java.util.List; 36 37/** 38 * A container containing child notifications 39 */ 40public class NotificationChildrenContainer extends ViewGroup { 41 42 private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; 43 private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5; 44 private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8; 45 46 private final int mChildPadding; 47 private final int mDividerHeight; 48 private final int mMaxNotificationHeight; 49 private final List<View> mDividers = new ArrayList<>(); 50 private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); 51 private final int mNotificationHeaderHeight; 52 private final int mNotificationAppearDistance; 53 private final int mNotificatonTopPadding; 54 private final HybridNotificationViewManager mHybridViewManager; 55 private final float mCollapsedBottompadding; 56 private ViewInvertHelper mOverflowInvertHelper; 57 private boolean mChildrenExpanded; 58 private ExpandableNotificationRow mNotificationParent; 59 private HybridNotificationView mGroupOverflowContainer; 60 private ViewState mGroupOverFlowState; 61 private int mRealHeight; 62 private int mLayoutDirection = LAYOUT_DIRECTION_UNDEFINED; 63 64 public NotificationChildrenContainer(Context context) { 65 this(context, null); 66 } 67 68 public NotificationChildrenContainer(Context context, AttributeSet attrs) { 69 this(context, attrs, 0); 70 } 71 72 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { 73 this(context, attrs, defStyleAttr, 0); 74 } 75 76 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, 77 int defStyleRes) { 78 super(context, attrs, defStyleAttr, defStyleRes); 79 mChildPadding = getResources().getDimensionPixelSize( 80 R.dimen.notification_children_padding); 81 mDividerHeight = Math.max(1, getResources().getDimensionPixelSize( 82 R.dimen.notification_divider_height)); 83 mMaxNotificationHeight = getResources().getDimensionPixelSize( 84 R.dimen.notification_max_height); 85 mNotificationAppearDistance = getResources().getDimensionPixelSize( 86 R.dimen.notification_appear_distance); 87 mNotificationHeaderHeight = getResources().getDimensionPixelSize( 88 com.android.internal.R.dimen.notification_content_margin_top); 89 mNotificatonTopPadding = getResources().getDimensionPixelSize( 90 R.dimen.notification_children_container_top_padding); 91 mCollapsedBottompadding = 11.5f * getResources().getDisplayMetrics().density; 92 mHybridViewManager = new HybridNotificationViewManager(getContext(), this); 93 } 94 95 @Override 96 protected void onLayout(boolean changed, int l, int t, int r, int b) { 97 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 98 for (int i = 0; i < childCount; i++) { 99 View child = mChildren.get(i); 100 if (child.getVisibility() == View.GONE) { 101 continue; 102 } 103 child.layout(0, 0, getWidth(), child.getMeasuredHeight()); 104 mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight); 105 } 106 if (mGroupOverflowContainer != null) { 107 mGroupOverflowContainer.layout(0, 0, getWidth(), 108 mGroupOverflowContainer.getMeasuredHeight()); 109 } 110 } 111 112 @Override 113 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 114 int ownMaxHeight = mMaxNotificationHeight; 115 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 116 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; 117 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; 118 int size = MeasureSpec.getSize(heightMeasureSpec); 119 if (hasFixedHeight || isHeightLimited) { 120 ownMaxHeight = Math.min(ownMaxHeight, size); 121 } 122 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); 123 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); 124 int height = mNotificationHeaderHeight + mNotificatonTopPadding; 125 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 126 for (int i = 0; i < childCount; i++) { 127 View child = mChildren.get(i); 128 child.measure(widthMeasureSpec, newHeightSpec); 129 height += child.getMeasuredHeight(); 130 131 // layout the divider 132 View divider = mDividers.get(i); 133 divider.measure(widthMeasureSpec, dividerHeightSpec); 134 height += mDividerHeight; 135 } 136 int width = MeasureSpec.getSize(widthMeasureSpec); 137 if (mGroupOverflowContainer != null) { 138 mGroupOverflowContainer.measure(widthMeasureSpec, newHeightSpec); 139 } 140 mRealHeight = height; 141 if (heightMode != MeasureSpec.UNSPECIFIED) { 142 height = Math.min(height, size); 143 } 144 setMeasuredDimension(width, height); 145 } 146 147 @Override 148 public boolean pointInView(float localX, float localY, float slop) { 149 return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && 150 localY < (mRealHeight + slop); 151 } 152 153 /** 154 * Add a child notification to this view. 155 * 156 * @param row the row to add 157 * @param childIndex the index to add it at, if -1 it will be added at the end 158 */ 159 public void addNotification(ExpandableNotificationRow row, int childIndex) { 160 int newIndex = childIndex < 0 ? mChildren.size() : childIndex; 161 mChildren.add(newIndex, row); 162 addView(row); 163 164 View divider = inflateDivider(); 165 addView(divider); 166 mDividers.add(newIndex, divider); 167 168 updateGroupOverflow(); 169 } 170 171 public void removeNotification(ExpandableNotificationRow row) { 172 int childIndex = mChildren.indexOf(row); 173 mChildren.remove(row); 174 removeView(row); 175 176 final View divider = mDividers.remove(childIndex); 177 removeView(divider); 178 getOverlay().add(divider); 179 CrossFadeHelper.fadeOut(divider, new Runnable() { 180 @Override 181 public void run() { 182 getOverlay().remove(divider); 183 } 184 }); 185 186 row.setSystemChildExpanded(false); 187 updateGroupOverflow(); 188 } 189 190 public void updateGroupOverflow() { 191 int childCount = mChildren.size(); 192 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 193 boolean hasOverflow = childCount > maxAllowedVisibleChildren; 194 int lastVisibleIndex = hasOverflow ? maxAllowedVisibleChildren - 2 195 : maxAllowedVisibleChildren - 1; 196 if (hasOverflow) { 197 mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup( 198 mGroupOverflowContainer, mChildren, lastVisibleIndex + 1); 199 if (mOverflowInvertHelper == null) { 200 mOverflowInvertHelper= new ViewInvertHelper(mGroupOverflowContainer, 201 NotificationPanelView.DOZE_ANIMATION_DURATION); 202 } 203 if (mGroupOverFlowState == null) { 204 mGroupOverFlowState = new ViewState(); 205 } 206 } else if (mGroupOverflowContainer != null) { 207 removeView(mGroupOverflowContainer); 208 mGroupOverflowContainer = null; 209 mOverflowInvertHelper = null; 210 mGroupOverFlowState = null; 211 } 212 } 213 214 @Override 215 protected void onConfigurationChanged(Configuration newConfig) { 216 super.onConfigurationChanged(newConfig); 217 int layoutDirection = getLayoutDirection(); 218 if (layoutDirection != mLayoutDirection) { 219 updateGroupOverflow(); 220 mLayoutDirection = layoutDirection; 221 } 222 } 223 224 private View inflateDivider() { 225 return LayoutInflater.from(mContext).inflate( 226 R.layout.notification_children_divider, this, false); 227 } 228 229 public List<ExpandableNotificationRow> getNotificationChildren() { 230 return mChildren; 231 } 232 233 /** 234 * Apply the order given in the list to the children. 235 * 236 * @param childOrder the new list order 237 * @return whether the list order has changed 238 */ 239 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 240 if (childOrder == null) { 241 return false; 242 } 243 boolean result = false; 244 for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { 245 ExpandableNotificationRow child = mChildren.get(i); 246 ExpandableNotificationRow desiredChild = childOrder.get(i); 247 if (child != desiredChild) { 248 mChildren.remove(desiredChild); 249 mChildren.add(i, desiredChild); 250 result = true; 251 } 252 } 253 updateExpansionStates(); 254 return result; 255 } 256 257 private void updateExpansionStates() { 258 // Let's make the first child expanded if the parent is 259 for (int i = 0; i < mChildren.size(); i++) { 260 ExpandableNotificationRow child = mChildren.get(i); 261 child.setSystemChildExpanded(false); 262 } 263 } 264 265 /** 266 * 267 * @return the intrinsic size of this children container, i.e the natural fully expanded state 268 */ 269 public int getIntrinsicHeight() { 270 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 271 return getIntrinsicHeight(maxAllowedVisibleChildren); 272 } 273 274 /** 275 * @return the intrinsic height with a number of children given 276 * in @param maxAllowedVisibleChildren 277 */ 278 private int getIntrinsicHeight(float maxAllowedVisibleChildren) { 279 int intrinsicHeight = mNotificationHeaderHeight; 280 if (mChildrenExpanded) { 281 intrinsicHeight += mNotificatonTopPadding; 282 } 283 int visibleChildren = 0; 284 int childCount = mChildren.size(); 285 for (int i = 0; i < childCount; i++) { 286 if (visibleChildren >= maxAllowedVisibleChildren) { 287 break; 288 } 289 ExpandableNotificationRow child = mChildren.get(i); 290 intrinsicHeight += child.getIntrinsicHeight(); 291 visibleChildren++; 292 } 293 if (visibleChildren > 0) { 294 if (mChildrenExpanded) { 295 intrinsicHeight += visibleChildren * mDividerHeight; 296 } else { 297 intrinsicHeight += (visibleChildren - 1) * mChildPadding; 298 } 299 } 300 if (!mChildrenExpanded) { 301 intrinsicHeight += mCollapsedBottompadding; 302 } 303 return intrinsicHeight; 304 } 305 306 /** 307 * Update the state of all its children based on a linear layout algorithm. 308 * 309 * @param resultState the state to update 310 * @param parentState the state of the parent 311 */ 312 public void getState(StackScrollState resultState, StackViewState parentState) { 313 int childCount = mChildren.size(); 314 int yPosition = mNotificationHeaderHeight; 315 boolean firstChild = true; 316 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 317 boolean hasOverflow = !mChildrenExpanded && childCount > maxAllowedVisibleChildren 318 && maxAllowedVisibleChildren != NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 319 int lastVisibleIndex = hasOverflow 320 ? maxAllowedVisibleChildren - 2 321 : maxAllowedVisibleChildren - 1; 322 for (int i = 0; i < childCount; i++) { 323 ExpandableNotificationRow child = mChildren.get(i); 324 if (!firstChild) { 325 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding; 326 } else { 327 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0; 328 firstChild = false; 329 } 330 StackViewState childState = resultState.getViewStateForView(child); 331 int intrinsicHeight = child.getIntrinsicHeight(); 332 childState.yTranslation = yPosition; 333 childState.zTranslation = 0; 334 childState.height = intrinsicHeight; 335 childState.dimmed = parentState.dimmed; 336 childState.dark = parentState.dark; 337 childState.hideSensitive = parentState.hideSensitive; 338 childState.belowSpeedBump = parentState.belowSpeedBump; 339 childState.clipTopAmount = 0; 340 childState.topOverLap = 0; 341 boolean visible = i <= lastVisibleIndex; 342 childState.alpha = visible ? 1 : 0; 343 childState.location = parentState.location; 344 yPosition += intrinsicHeight; 345 } 346 if (mGroupOverflowContainer != null) { 347 mGroupOverFlowState.initFrom(mGroupOverflowContainer); 348 if (hasOverflow) { 349 StackViewState firstOverflowState = 350 resultState.getViewStateForView(mChildren.get(lastVisibleIndex + 1)); 351 mGroupOverFlowState.yTranslation = firstOverflowState.yTranslation; 352 } 353 mGroupOverFlowState.alpha = mChildrenExpanded || !hasOverflow ? 0.0f : 1.0f; 354 } 355 } 356 357 private int getMaxAllowedVisibleChildren() { 358 return getMaxAllowedVisibleChildren(false /* likeCollapsed */); 359 } 360 361 private int getMaxAllowedVisibleChildren(boolean likeCollapsed) { 362 if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) { 363 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 364 } 365 if (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp()) { 366 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; 367 } 368 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; 369 } 370 371 public void applyState(StackScrollState state) { 372 int childCount = mChildren.size(); 373 ViewState tmpState = new ViewState(); 374 for (int i = 0; i < childCount; i++) { 375 ExpandableNotificationRow child = mChildren.get(i); 376 StackViewState viewState = state.getViewStateForView(child); 377 state.applyState(child, viewState); 378 379 // layout the divider 380 View divider = mDividers.get(i); 381 tmpState.initFrom(divider); 382 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 383 tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 384 state.applyViewState(divider, tmpState); 385 } 386 if (mGroupOverflowContainer != null) { 387 state.applyViewState(mGroupOverflowContainer, mGroupOverFlowState); 388 } 389 } 390 391 /** 392 * This is called when the children expansion has changed and positions the children properly 393 * for an appear animation. 394 * 395 * @param state the new state we animate to 396 */ 397 public void prepareExpansionChanged(StackScrollState state) { 398 // TODO: do something that makes sense, like placing the invisible views correctly 399 return; 400 } 401 402 public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, 403 long baseDelay, long duration) { 404 int childCount = mChildren.size(); 405 ViewState tmpState = new ViewState(); 406 for (int i = childCount - 1; i >= 0; i--) { 407 ExpandableNotificationRow child = mChildren.get(i); 408 StackViewState viewState = state.getViewStateForView(child); 409 stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay); 410 411 // layout the divider 412 View divider = mDividers.get(i); 413 tmpState.initFrom(divider); 414 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 415 tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 416 stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration); 417 } 418 if (mGroupOverflowContainer != null) { 419 stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, 420 baseDelay, duration); 421 } 422 } 423 424 public ExpandableNotificationRow getViewAtPosition(float y) { 425 // find the view under the pointer, accounting for GONE views 426 final int count = mChildren.size(); 427 for (int childIdx = 0; childIdx < count; childIdx++) { 428 ExpandableNotificationRow slidingChild = mChildren.get(childIdx); 429 float childTop = slidingChild.getTranslationY(); 430 float top = childTop + slidingChild.getClipTopAmount(); 431 float bottom = childTop + slidingChild.getActualHeight(); 432 if (y >= top && y <= bottom) { 433 return slidingChild; 434 } 435 } 436 return null; 437 } 438 439 public void setChildrenExpanded(boolean childrenExpanded) { 440 mChildrenExpanded = childrenExpanded; 441 } 442 443 public void setNotificationParent(ExpandableNotificationRow parent) { 444 mNotificationParent = parent; 445 } 446 447 public int getMaxContentHeight() { 448 return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 449 } 450 451 public int getMinHeight() { 452 return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED); 453 } 454 455 public int getMinExpandHeight() { 456 return getIntrinsicHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */)); 457 } 458 459 public void setDark(boolean dark, boolean fade, long delay) { 460 if (mGroupOverflowContainer != null) { 461 mOverflowInvertHelper.setInverted(dark, fade, delay); 462 } 463 } 464} 465