1 /* 2 * Copyright (C) 2016 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 17 package com.android.systemui.statusbar; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.os.SystemProperties; 22 import android.util.AttributeSet; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.view.accessibility.AccessibilityNodeInfo; 26 27 import com.android.systemui.Interpolators; 28 import com.android.systemui.R; 29 import com.android.systemui.ViewInvertHelper; 30 import com.android.systemui.statusbar.notification.NotificationUtils; 31 import com.android.systemui.statusbar.phone.NotificationIconContainer; 32 import com.android.systemui.statusbar.phone.NotificationPanelView; 33 import com.android.systemui.statusbar.stack.AmbientState; 34 import com.android.systemui.statusbar.stack.AnimationProperties; 35 import com.android.systemui.statusbar.stack.ExpandableViewState; 36 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 37 import com.android.systemui.statusbar.stack.StackScrollState; 38 import com.android.systemui.statusbar.stack.ViewState; 39 40 /** 41 * A notification shelf view that is placed inside the notification scroller. It manages the 42 * overflow icons that don't fit into the regular list anymore. 43 */ 44 public class NotificationShelf extends ActivatableNotificationView implements 45 View.OnLayoutChangeListener { 46 47 public static final boolean SHOW_AMBIENT_ICONS = true; 48 private static final boolean USE_ANIMATIONS_WHEN_OPENING = 49 SystemProperties.getBoolean("debug.icon_opening_animations", true); 50 private static final boolean ICON_ANMATIONS_WHILE_SCROLLING 51 = SystemProperties.getBoolean("debug.icon_scroll_animations", true); 52 private ViewInvertHelper mViewInvertHelper; 53 private boolean mDark; 54 private NotificationIconContainer mShelfIcons; 55 private ShelfState mShelfState; 56 private int[] mTmp = new int[2]; 57 private boolean mHideBackground; 58 private int mIconAppearTopPadding; 59 private int mStatusBarHeight; 60 private int mStatusBarPaddingStart; 61 private AmbientState mAmbientState; 62 private NotificationStackScrollLayout mHostLayout; 63 private int mMaxLayoutHeight; 64 private int mPaddingBetweenElements; 65 private int mNotGoneIndex; 66 private boolean mHasItemsInStableShelf; 67 private NotificationIconContainer mCollapsedIcons; 68 private int mScrollFastThreshold; 69 private int mStatusBarState; 70 private float mMaxShelfEnd; 71 private int mRelativeOffset; 72 private boolean mInteractive; 73 private float mOpenedAmount; 74 private boolean mNoAnimationsInThisFrame; 75 private boolean mAnimationsEnabled = true; 76 NotificationShelf(Context context, AttributeSet attrs)77 public NotificationShelf(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 } 80 81 @Override onFinishInflate()82 protected void onFinishInflate() { 83 super.onFinishInflate(); 84 mShelfIcons = findViewById(R.id.content); 85 mShelfIcons.setClipChildren(false); 86 mShelfIcons.setClipToPadding(false); 87 88 setClipToActualHeight(false); 89 setClipChildren(false); 90 setClipToPadding(false); 91 mShelfIcons.setShowAllIcons(false); 92 mViewInvertHelper = new ViewInvertHelper(mShelfIcons, 93 NotificationPanelView.DOZE_ANIMATION_DURATION); 94 mShelfState = new ShelfState(); 95 initDimens(); 96 } 97 bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout)98 public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) { 99 mAmbientState = ambientState; 100 mHostLayout = hostLayout; 101 } 102 initDimens()103 private void initDimens() { 104 mIconAppearTopPadding = getResources().getDimensionPixelSize( 105 R.dimen.notification_icon_appear_padding); 106 mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); 107 mStatusBarPaddingStart = getResources().getDimensionPixelOffset( 108 R.dimen.status_bar_padding_start); 109 mPaddingBetweenElements = getResources().getDimensionPixelSize( 110 R.dimen.notification_divider_height); 111 ViewGroup.LayoutParams layoutParams = getLayoutParams(); 112 layoutParams.height = getResources().getDimensionPixelOffset( 113 R.dimen.notification_shelf_height); 114 setLayoutParams(layoutParams); 115 int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding); 116 mShelfIcons.setPadding(padding, 0, padding, 0); 117 mScrollFastThreshold = getResources().getDimensionPixelOffset( 118 R.dimen.scroll_fast_threshold); 119 } 120 121 @Override onConfigurationChanged(Configuration newConfig)122 protected void onConfigurationChanged(Configuration newConfig) { 123 super.onConfigurationChanged(newConfig); 124 initDimens(); 125 } 126 127 @Override setDark(boolean dark, boolean fade, long delay)128 public void setDark(boolean dark, boolean fade, long delay) { 129 super.setDark(dark, fade, delay); 130 if (mDark == dark) return; 131 mDark = dark; 132 mShelfIcons.setDark(dark, fade, delay); 133 updateInteractiveness(); 134 } 135 136 @Override getContentView()137 protected View getContentView() { 138 return mShelfIcons; 139 } 140 getShelfIcons()141 public NotificationIconContainer getShelfIcons() { 142 return mShelfIcons; 143 } 144 145 @Override createNewViewState(StackScrollState stackScrollState)146 public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { 147 return mShelfState; 148 } 149 updateState(StackScrollState resultState, AmbientState ambientState)150 public void updateState(StackScrollState resultState, 151 AmbientState ambientState) { 152 View lastView = ambientState.getLastVisibleBackgroundChild(); 153 if (lastView != null) { 154 float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding() 155 + ambientState.getStackTranslation(); 156 ExpandableViewState lastViewState = resultState.getViewStateForView(lastView); 157 float viewEnd = lastViewState.yTranslation + lastViewState.height; 158 mShelfState.copyFrom(lastViewState); 159 mShelfState.height = getIntrinsicHeight(); 160 mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height, 161 getFullyClosedTranslation()); 162 mShelfState.zTranslation = ambientState.getBaseZHeight(); 163 float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation()) 164 / (getIntrinsicHeight() * 2); 165 openedAmount = Math.min(1.0f, openedAmount); 166 mShelfState.openedAmount = openedAmount; 167 mShelfState.clipTopAmount = 0; 168 mShelfState.alpha = mAmbientState.hasPulsingNotifications() ? 0 : 1; 169 mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0; 170 mShelfState.shadowAlpha = 1.0f; 171 mShelfState.hideSensitive = false; 172 mShelfState.xTranslation = getTranslationX(); 173 if (mNotGoneIndex != -1) { 174 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex); 175 } 176 mShelfState.hasItemsInStableShelf = lastViewState.inShelf; 177 mShelfState.hidden = !mAmbientState.isShadeExpanded(); 178 mShelfState.maxShelfEnd = maxShelfEnd; 179 } else { 180 mShelfState.hidden = true; 181 mShelfState.location = ExpandableViewState.LOCATION_GONE; 182 mShelfState.hasItemsInStableShelf = false; 183 } 184 } 185 186 /** 187 * Update the shelf appearance based on the other notifications around it. This transforms 188 * the icons from the notification area into the shelf. 189 */ updateAppearance()190 public void updateAppearance() { 191 mShelfIcons.resetViewStates(); 192 float shelfStart = getTranslationY(); 193 float numViewsInShelf = 0.0f; 194 View lastChild = mAmbientState.getLastVisibleBackgroundChild(); 195 mNotGoneIndex = -1; 196 float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; 197 float expandAmount = 0.0f; 198 if (shelfStart >= interpolationStart) { 199 expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight(); 200 expandAmount = Math.min(1.0f, expandAmount); 201 } 202 // find the first view that doesn't overlap with the shelf 203 int notificationIndex = 0; 204 int notGoneIndex = 0; 205 int colorOfViewBeforeLast = NO_COLOR; 206 boolean backgroundForceHidden = false; 207 if (mHideBackground && !mShelfState.hasItemsInStableShelf) { 208 backgroundForceHidden = true; 209 } 210 int colorTwoBefore = NO_COLOR; 211 int previousColor = NO_COLOR; 212 float transitionAmount = 0.0f; 213 float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity(); 214 boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold 215 || (mAmbientState.isExpansionChanging() 216 && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); 217 boolean scrolling = currentScrollVelocity > 0; 218 boolean expandingAnimated = mAmbientState.isExpansionChanging() 219 && !mAmbientState.isPanelTracking(); 220 int baseZHeight = mAmbientState.getBaseZHeight(); 221 while (notificationIndex < mHostLayout.getChildCount()) { 222 ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex); 223 notificationIndex++; 224 if (!(child instanceof ExpandableNotificationRow) 225 || child.getVisibility() == GONE) { 226 continue; 227 } 228 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 229 float notificationClipEnd; 230 boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight; 231 boolean isLastChild = child == lastChild; 232 float rowTranslationY = row.getTranslationY(); 233 if (isLastChild || aboveShelf || backgroundForceHidden) { 234 notificationClipEnd = shelfStart + getIntrinsicHeight(); 235 } else { 236 notificationClipEnd = shelfStart - mPaddingBetweenElements; 237 float height = notificationClipEnd - rowTranslationY; 238 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) { 239 // We want the gap to close when we reached the minimum size and only shrink 240 // before 241 notificationClipEnd = Math.min(shelfStart, 242 rowTranslationY + getNotificationMergeSize()); 243 } 244 } 245 updateNotificationClipHeight(row, notificationClipEnd); 246 float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast, 247 expandingAnimated, isLastChild); 248 numViewsInShelf += inShelfAmount; 249 int ownColorUntinted = row.getBackgroundColorWithoutTint(); 250 if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) { 251 mNotGoneIndex = notGoneIndex; 252 setTintColor(previousColor); 253 setOverrideTintColor(colorTwoBefore, transitionAmount); 254 255 } else if (mNotGoneIndex == -1) { 256 colorTwoBefore = previousColor; 257 transitionAmount = inShelfAmount; 258 } 259 if (isLastChild) { 260 if (colorOfViewBeforeLast == NO_COLOR) { 261 colorOfViewBeforeLast = ownColorUntinted; 262 } 263 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount); 264 } else { 265 colorOfViewBeforeLast = ownColorUntinted; 266 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */); 267 } 268 if (notGoneIndex != 0 || !aboveShelf) { 269 row.setAboveShelf(false); 270 } 271 notGoneIndex++; 272 previousColor = ownColorUntinted; 273 } 274 mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex()); 275 mShelfIcons.calculateIconTranslations(); 276 mShelfIcons.applyIconStates(); 277 boolean hideBackground = numViewsInShelf < 1.0f; 278 setHideBackground(hideBackground || backgroundForceHidden); 279 if (mNotGoneIndex == -1) { 280 mNotGoneIndex = notGoneIndex; 281 } 282 } 283 284 private void updateNotificationClipHeight(ExpandableNotificationRow row, 285 float notificationClipEnd) { 286 float viewEnd = row.getTranslationY() + row.getActualHeight(); 287 boolean isPinned = row.isPinned() || row.isHeadsUpAnimatingAway(); 288 if (viewEnd > notificationClipEnd 289 && (mAmbientState.isShadeExpanded() || !isPinned)) { 290 int clipBottomAmount = (int) (viewEnd - notificationClipEnd); 291 if (isPinned) { 292 clipBottomAmount = Math.min(row.getIntrinsicHeight() - row.getCollapsedHeight(), 293 clipBottomAmount); 294 } 295 row.setClipBottomAmount(clipBottomAmount); 296 } else { 297 row.setClipBottomAmount(0); 298 } 299 } 300 301 /** 302 * @return the icon amount how much this notification is in the shelf; 303 */ 304 private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount, 305 boolean scrolling, boolean scrollingFast, boolean expandingAnimated, 306 boolean isLastChild) { 307 // Let calculate how much the view is in the shelf 308 float viewStart = row.getTranslationY(); 309 int fullHeight = row.getActualHeight() + mPaddingBetweenElements; 310 float iconTransformDistance = getIntrinsicHeight() * 1.5f; 311 iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount); 312 if (isLastChild) { 313 fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight()); 314 iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight() 315 - getIntrinsicHeight()); 316 } 317 float viewEnd = viewStart + fullHeight; 318 float fullTransitionAmount; 319 float iconTransitionAmount; 320 float shelfStart = getTranslationY(); 321 if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf()) 322 && (mAmbientState.isShadeExpanded() 323 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) { 324 if (viewStart < shelfStart) { 325 326 float fullAmount = (shelfStart - viewStart) / fullHeight; 327 float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( 328 fullAmount); 329 interpolatedAmount = NotificationUtils.interpolate( 330 interpolatedAmount, fullAmount, expandAmount); 331 fullTransitionAmount = 1.0f - interpolatedAmount; 332 333 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance; 334 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount); 335 iconTransitionAmount = 1.0f - iconTransitionAmount; 336 337 } else { 338 fullTransitionAmount = 1.0f; 339 iconTransitionAmount = 1.0f; 340 } 341 } else { 342 fullTransitionAmount = 0.0f; 343 iconTransitionAmount = 0.0f; 344 } 345 updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount, 346 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); 347 return fullTransitionAmount; 348 } 349 350 private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount, 351 float fullTransitionAmount, float iconTransformDistance, boolean scrolling, 352 boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { 353 StatusBarIconView icon = row.getEntry().expandedIcon; 354 NotificationIconContainer.IconState iconState = getIconState(icon); 355 if (iconState == null) { 356 return; 357 } 358 float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f; 359 if (clampedAmount == fullTransitionAmount) { 360 iconState.noAnimations = scrollingFast || expandingAnimated; 361 iconState.useFullTransitionAmount = iconState.noAnimations 362 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling); 363 iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING 364 && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging(); 365 iconState.translateContent = mMaxLayoutHeight - getTranslationY() 366 - getIntrinsicHeight() > 0; 367 } 368 if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount 369 && !ViewState.isAnimatingY(icon))) { 370 iconState.cancelAnimations(icon); 371 iconState.useFullTransitionAmount = true; 372 iconState.noAnimations = true; 373 } 374 float transitionAmount; 375 if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount 376 || iconState.useLinearTransitionAmount) { 377 transitionAmount = iconTransitionAmount; 378 } else { 379 // We take the clamped position instead 380 transitionAmount = clampedAmount; 381 iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount 382 && !mNoAnimationsInThisFrame; 383 } 384 iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING 385 || iconState.useFullTransitionAmount 386 ? fullTransitionAmount 387 : transitionAmount; 388 iconState.clampedAppearAmount = clampedAmount; 389 float contentTransformationAmount = !row.isAboveShelf() 390 && (isLastChild || iconState.translateContent) 391 ? iconTransitionAmount 392 : 0.0f; 393 row.setContentTransformationAmount(contentTransformationAmount, isLastChild); 394 setIconTransformationAmount(row, transitionAmount, iconTransformDistance, 395 clampedAmount != transitionAmount, isLastChild); 396 } 397 398 private void setIconTransformationAmount(ExpandableNotificationRow row, 399 float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation, 400 boolean isLastChild) { 401 StatusBarIconView icon = row.getEntry().expandedIcon; 402 NotificationIconContainer.IconState iconState = getIconState(icon); 403 404 View rowIcon = row.getNotificationIcon(); 405 float notificationIconPosition = row.getTranslationY() + row.getContentTranslation(); 406 boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); 407 if (usingLinearInterpolation && !stayingInShelf) { 408 // If we interpolate from the notification position, this might lead to a slightly 409 // odd interpolation, since the notification position changes as well. Let's interpolate 410 // from a fixed distance. We can only do this if we don't animate and the icon is 411 // always in the interpolated positon. 412 notificationIconPosition = getTranslationY() - iconTransformDistance; 413 } 414 float notificationIconSize = 0.0f; 415 int iconTopPadding; 416 if (rowIcon != null) { 417 iconTopPadding = row.getRelativeTopPadding(rowIcon); 418 notificationIconSize = rowIcon.getHeight(); 419 } else { 420 iconTopPadding = mIconAppearTopPadding; 421 } 422 notificationIconPosition += iconTopPadding; 423 float shelfIconPosition = getTranslationY() + icon.getTop(); 424 shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f; 425 float iconYTranslation = NotificationUtils.interpolate( 426 notificationIconPosition - shelfIconPosition, 427 0, 428 transitionAmount); 429 float shelfIconSize = icon.getHeight() * icon.getIconScale(); 430 float alpha = 1.0f; 431 boolean noIcon = !row.isShowingIcon(); 432 if (noIcon) { 433 // The view currently doesn't have an icon, lets transform it in! 434 alpha = transitionAmount; 435 notificationIconSize = shelfIconSize / 2.0f; 436 } 437 // The notification size is different from the size in the shelf / statusbar 438 float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, 439 transitionAmount); 440 if (iconState != null) { 441 iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale(); 442 iconState.scaleY = iconState.scaleX; 443 iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon); 444 iconState.alpha = alpha; 445 iconState.yTranslation = iconYTranslation; 446 if (stayingInShelf) { 447 iconState.iconAppearAmount = 1.0f; 448 iconState.alpha = 1.0f; 449 iconState.scaleX = 1.0f; 450 iconState.scaleY = 1.0f; 451 iconState.hidden = false; 452 } 453 if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed() 454 || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) { 455 iconState.hidden = true; 456 } 457 int backgroundColor = getBackgroundColorWithoutTint(); 458 int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor); 459 if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { 460 int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor(); 461 shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor, 462 iconState.iconAppearAmount); 463 } 464 iconState.iconColor = shelfColor; 465 } 466 } 467 468 private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) { 469 return mShelfIcons.getIconState(icon); 470 } 471 472 private float getFullyClosedTranslation() { 473 return - (getIntrinsicHeight() - mStatusBarHeight) / 2; 474 } 475 476 public int getNotificationMergeSize() { 477 return getIntrinsicHeight(); 478 } 479 480 @Override 481 public boolean hasNoContentHeight() { 482 return true; 483 } 484 485 private void setHideBackground(boolean hideBackground) { 486 if (mHideBackground != hideBackground) { 487 mHideBackground = hideBackground; 488 updateBackground(); 489 updateOutline(); 490 } 491 } 492 493 public boolean hidesBackground() { 494 return mHideBackground; 495 } 496 497 @Override 498 protected boolean needsOutline() { 499 return !mHideBackground && super.needsOutline(); 500 } 501 502 @Override 503 protected boolean shouldHideBackground() { 504 return super.shouldHideBackground() || mHideBackground; 505 } 506 507 @Override 508 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 509 super.onLayout(changed, left, top, right, bottom); 510 updateRelativeOffset(); 511 } 512 513 private void updateRelativeOffset() { 514 mCollapsedIcons.getLocationOnScreen(mTmp); 515 mRelativeOffset = mTmp[0]; 516 getLocationOnScreen(mTmp); 517 mRelativeOffset -= mTmp[0]; 518 } 519 520 private void setOpenedAmount(float openedAmount) { 521 mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f; 522 mOpenedAmount = openedAmount; 523 if (!mAmbientState.isPanelFullWidth()) { 524 // We don't do a transformation at all, lets just assume we are fully opened 525 openedAmount = 1.0f; 526 } 527 int start = mRelativeOffset; 528 if (isLayoutRtl()) { 529 start = getWidth() - start - mCollapsedIcons.getWidth(); 530 } 531 int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), 532 mShelfIcons.getWidth(), 533 openedAmount); 534 mShelfIcons.setActualLayoutWidth(width); 535 boolean hasOverflow = mCollapsedIcons.hasOverflow(); 536 int collapsedPadding = mCollapsedIcons.getPaddingEnd(); 537 if (!hasOverflow) { 538 // we have to ensure that adding the low priority notification won't lead to an 539 // overflow 540 collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT) 541 * mCollapsedIcons.getIconSize(); 542 } 543 float padding = NotificationUtils.interpolate(collapsedPadding, 544 mShelfIcons.getPaddingEnd(), 545 openedAmount); 546 mShelfIcons.setActualPaddingEnd(padding); 547 float paddingStart = NotificationUtils.interpolate(start, 548 mShelfIcons.getPaddingStart(), openedAmount); 549 mShelfIcons.setActualPaddingStart(paddingStart); 550 mShelfIcons.setOpenedAmount(openedAmount); 551 mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption()); 552 } 553 554 public void setMaxLayoutHeight(int maxLayoutHeight) { 555 mMaxLayoutHeight = maxLayoutHeight; 556 } 557 558 /** 559 * @return the index of the notification at which the shelf visually resides 560 */ 561 public int getNotGoneIndex() { 562 return mNotGoneIndex; 563 } 564 565 private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) { 566 if (mHasItemsInStableShelf != hasItemsInStableShelf) { 567 mHasItemsInStableShelf = hasItemsInStableShelf; 568 updateInteractiveness(); 569 } 570 } 571 572 /** 573 * @return whether the shelf has any icons in it when a potential animation has finished, i.e 574 * if the current state would be applied right now 575 */ 576 public boolean hasItemsInStableShelf() { 577 return mHasItemsInStableShelf; 578 } 579 580 public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { 581 mCollapsedIcons = collapsedIcons; 582 mCollapsedIcons.addOnLayoutChangeListener(this); 583 } 584 585 public void setStatusBarState(int statusBarState) { 586 if (mStatusBarState != statusBarState) { 587 mStatusBarState = statusBarState; 588 updateInteractiveness(); 589 } 590 } 591 592 private void updateInteractiveness() { 593 mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf 594 && !mDark; 595 setClickable(mInteractive); 596 setFocusable(mInteractive); 597 setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES 598 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 599 } 600 601 @Override 602 protected boolean isInteractive() { 603 return mInteractive; 604 } 605 606 public void setMaxShelfEnd(float maxShelfEnd) { 607 mMaxShelfEnd = maxShelfEnd; 608 } 609 610 public void setAnimationsEnabled(boolean enabled) { 611 mAnimationsEnabled = enabled; 612 mCollapsedIcons.setAnimationsEnabled(enabled); 613 if (!enabled) { 614 // we need to wait with enabling the animations until the first frame has passed 615 mShelfIcons.setAnimationsEnabled(false); 616 } 617 } 618 619 @Override 620 public boolean hasOverlappingRendering() { 621 return false; // Shelf only uses alpha for transitions where the difference can't be seen. 622 } 623 624 @Override 625 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 626 super.onInitializeAccessibilityNodeInfo(info); 627 if (mInteractive) { 628 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); 629 AccessibilityNodeInfo.AccessibilityAction unlock 630 = new AccessibilityNodeInfo.AccessibilityAction( 631 AccessibilityNodeInfo.ACTION_CLICK, 632 getContext().getString(R.string.accessibility_overflow_action)); 633 info.addAction(unlock); 634 } 635 } 636 637 @Override 638 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, 639 int oldTop, int oldRight, int oldBottom) { 640 updateRelativeOffset(); 641 } 642 643 private class ShelfState extends ExpandableViewState { 644 private float openedAmount; 645 private boolean hasItemsInStableShelf; 646 private float maxShelfEnd; 647 648 @Override 649 public void applyToView(View view) { 650 super.applyToView(view); 651 setMaxShelfEnd(maxShelfEnd); 652 setOpenedAmount(openedAmount); 653 updateAppearance(); 654 setHasItemsInStableShelf(hasItemsInStableShelf); 655 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); 656 } 657 658 @Override 659 public void animateTo(View child, AnimationProperties properties) { 660 super.animateTo(child, properties); 661 setMaxShelfEnd(maxShelfEnd); 662 setOpenedAmount(openedAmount); 663 updateAppearance(); 664 setHasItemsInStableShelf(hasItemsInStableShelf); 665 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); 666 } 667 } 668 } 669