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.phone; 18 19 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; 20 import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; 21 22 import android.animation.Animator; 23 import android.animation.AnimatorListenerAdapter; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Paint; 29 import android.graphics.Rect; 30 import android.graphics.drawable.Icon; 31 import android.util.AttributeSet; 32 import android.util.Property; 33 import android.view.View; 34 import android.view.animation.Interpolator; 35 36 import androidx.collection.ArrayMap; 37 38 import com.android.internal.statusbar.StatusBarIcon; 39 import com.android.systemui.Interpolators; 40 import com.android.systemui.R; 41 import com.android.systemui.statusbar.AlphaOptimizedFrameLayout; 42 import com.android.systemui.statusbar.StatusBarIconView; 43 import com.android.systemui.statusbar.notification.stack.AnimationFilter; 44 import com.android.systemui.statusbar.notification.stack.AnimationProperties; 45 import com.android.systemui.statusbar.notification.stack.ViewState; 46 47 import java.util.ArrayList; 48 import java.util.HashMap; 49 import java.util.function.Consumer; 50 51 /** 52 * A container for notification icons. It handles overflowing icons properly and positions them 53 * correctly on the screen. 54 */ 55 public class NotificationIconContainer extends AlphaOptimizedFrameLayout { 56 /** 57 * A float value indicating how much before the overflow start the icons should transform into 58 * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts 59 * 1 icon width early. 60 */ 61 public static final float OVERFLOW_EARLY_AMOUNT = 0.2f; 62 private static final int NO_VALUE = Integer.MIN_VALUE; 63 private static final String TAG = "NotificationIconContainer"; 64 private static final boolean DEBUG = false; 65 private static final boolean DEBUG_OVERFLOW = false; 66 private static final int CANNED_ANIMATION_DURATION = 100; 67 private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { 68 private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); 69 70 @Override 71 public AnimationFilter getAnimationFilter() { 72 return mAnimationFilter; 73 } 74 }.setDuration(200); 75 76 private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() { 77 private AnimationFilter mAnimationFilter = new AnimationFilter() 78 .animateX() 79 .animateY() 80 .animateAlpha() 81 .animateScale(); 82 83 @Override 84 public AnimationFilter getAnimationFilter() { 85 return mAnimationFilter; 86 } 87 88 }.setDuration(CANNED_ANIMATION_DURATION); 89 90 /** 91 * Temporary AnimationProperties to avoid unnecessary allocations. 92 */ 93 private static final AnimationProperties sTempProperties = new AnimationProperties() { 94 private AnimationFilter mAnimationFilter = new AnimationFilter(); 95 96 @Override 97 public AnimationFilter getAnimationFilter() { 98 return mAnimationFilter; 99 } 100 }; 101 102 private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() { 103 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); 104 105 @Override 106 public AnimationFilter getAnimationFilter() { 107 return mAnimationFilter; 108 } 109 }.setDuration(200).setDelay(50); 110 111 /** 112 * The animation property used for all icons that were not isolated, when the isolation ends. 113 * This just fades the alpha and doesn't affect the movement and has a delay. 114 */ 115 private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS 116 = new AnimationProperties() { 117 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); 118 119 @Override 120 public AnimationFilter getAnimationFilter() { 121 return mAnimationFilter; 122 } 123 }.setDuration(CONTENT_FADE_DURATION); 124 125 /** 126 * The animation property used for the icon when its isolation ends. 127 * This animates the translation back to the right position. 128 */ 129 private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() { 130 private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); 131 132 @Override 133 public AnimationFilter getAnimationFilter() { 134 return mAnimationFilter; 135 } 136 }.setDuration(CONTENT_FADE_DURATION); 137 138 private static final int MAX_VISIBLE_ICONS_ON_LOCK = 5; 139 public static final int MAX_STATIC_ICONS = 4; 140 private static final int MAX_DOTS = 1; 141 142 private boolean mIsStaticLayout = true; 143 private final HashMap<View, IconState> mIconStates = new HashMap<>(); 144 private int mDotPadding; 145 private int mStaticDotRadius; 146 private int mStaticDotDiameter; 147 private int mOverflowWidth; 148 private int mActualLayoutWidth = NO_VALUE; 149 private float mActualPaddingEnd = NO_VALUE; 150 private float mActualPaddingStart = NO_VALUE; 151 private boolean mDozing; 152 private boolean mOnLockScreen; 153 private boolean mChangingViewPositions; 154 private int mAddAnimationStartIndex = -1; 155 private int mCannedAnimationStartIndex = -1; 156 private int mSpeedBumpIndex = -1; 157 private int mIconSize; 158 private float mOpenedAmount = 0.0f; 159 private boolean mDisallowNextAnimation; 160 private boolean mAnimationsEnabled = true; 161 private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; 162 // Keep track of the last visible icon so collapsed container can report on its location 163 private IconState mLastVisibleIconState; 164 private IconState mFirstVisibleIconState; 165 private float mVisualOverflowStart; 166 // Keep track of overflow in range [0, 3] 167 private int mNumDots; 168 private StatusBarIconView mIsolatedIcon; 169 private Rect mIsolatedIconLocation; 170 private int[] mAbsolutePosition = new int[2]; 171 private View mIsolatedIconForAnimation; 172 NotificationIconContainer(Context context, AttributeSet attrs)173 public NotificationIconContainer(Context context, AttributeSet attrs) { 174 super(context, attrs); 175 initDimens(); 176 setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW)); 177 } 178 initDimens()179 private void initDimens() { 180 mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); 181 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); 182 mStaticDotDiameter = 2 * mStaticDotRadius; 183 } 184 185 @Override onDraw(Canvas canvas)186 protected void onDraw(Canvas canvas) { 187 super.onDraw(canvas); 188 Paint paint = new Paint(); 189 paint.setColor(Color.RED); 190 paint.setStyle(Paint.Style.STROKE); 191 canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint); 192 193 if (DEBUG_OVERFLOW) { 194 if (mLastVisibleIconState == null) { 195 return; 196 } 197 198 int height = getHeight(); 199 int end = getFinalTranslationX(); 200 201 // Visualize the "end" of the layout 202 paint.setColor(Color.BLUE); 203 canvas.drawLine(end, 0, end, height, paint); 204 205 paint.setColor(Color.GREEN); 206 int lastIcon = (int) mLastVisibleIconState.xTranslation; 207 canvas.drawLine(lastIcon, 0, lastIcon, height, paint); 208 209 if (mFirstVisibleIconState != null) { 210 int firstIcon = (int) mFirstVisibleIconState.xTranslation; 211 canvas.drawLine(firstIcon, 0, firstIcon, height, paint); 212 } 213 214 paint.setColor(Color.RED); 215 canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint); 216 217 paint.setColor(Color.YELLOW); 218 float overflow = getMaxOverflowStart(); 219 canvas.drawLine(overflow, 0, overflow, height, paint); 220 } 221 } 222 223 @Override onConfigurationChanged(Configuration newConfig)224 protected void onConfigurationChanged(Configuration newConfig) { 225 super.onConfigurationChanged(newConfig); 226 initDimens(); 227 } 228 229 @Override onLayout(boolean changed, int l, int t, int r, int b)230 protected void onLayout(boolean changed, int l, int t, int r, int b) { 231 float centerY = getHeight() / 2.0f; 232 // we layout all our children on the left at the top 233 mIconSize = 0; 234 for (int i = 0; i < getChildCount(); i++) { 235 View child = getChildAt(i); 236 // We need to layout all children even the GONE ones, such that the heights are 237 // calculated correctly as they are used to calculate how many we can fit on the screen 238 int width = child.getMeasuredWidth(); 239 int height = child.getMeasuredHeight(); 240 int top = (int) (centerY - height / 2.0f); 241 child.layout(0, top, width, top + height); 242 if (i == 0) { 243 setIconSize(child.getWidth()); 244 } 245 } 246 getLocationOnScreen(mAbsolutePosition); 247 if (mIsStaticLayout) { 248 updateState(); 249 } 250 } 251 setIconSize(int size)252 private void setIconSize(int size) { 253 mIconSize = size; 254 mOverflowWidth = mIconSize + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding); 255 } 256 updateState()257 private void updateState() { 258 resetViewStates(); 259 calculateIconTranslations(); 260 applyIconStates(); 261 } 262 applyIconStates()263 public void applyIconStates() { 264 for (int i = 0; i < getChildCount(); i++) { 265 View child = getChildAt(i); 266 ViewState childState = mIconStates.get(child); 267 if (childState != null) { 268 childState.applyToView(child); 269 } 270 } 271 mAddAnimationStartIndex = -1; 272 mCannedAnimationStartIndex = -1; 273 mDisallowNextAnimation = false; 274 mIsolatedIconForAnimation = null; 275 } 276 277 @Override onViewAdded(View child)278 public void onViewAdded(View child) { 279 super.onViewAdded(child); 280 boolean isReplacingIcon = isReplacingIcon(child); 281 if (!mChangingViewPositions) { 282 IconState v = new IconState(child); 283 if (isReplacingIcon) { 284 v.justAdded = false; 285 v.justReplaced = true; 286 } 287 mIconStates.put(child, v); 288 } 289 int childIndex = indexOfChild(child); 290 if (childIndex < getChildCount() - 1 && !isReplacingIcon 291 && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) { 292 if (mAddAnimationStartIndex < 0) { 293 mAddAnimationStartIndex = childIndex; 294 } else { 295 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); 296 } 297 } 298 if (child instanceof StatusBarIconView) { 299 ((StatusBarIconView) child).setDozing(mDozing, false, 0); 300 } 301 } 302 isReplacingIcon(View child)303 private boolean isReplacingIcon(View child) { 304 if (mReplacingIcons == null) { 305 return false; 306 } 307 if (!(child instanceof StatusBarIconView)) { 308 return false; 309 } 310 StatusBarIconView iconView = (StatusBarIconView) child; 311 Icon sourceIcon = iconView.getSourceIcon(); 312 String groupKey = iconView.getNotification().getGroupKey(); 313 ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey); 314 if (statusBarIcons != null) { 315 StatusBarIcon replacedIcon = statusBarIcons.get(0); 316 if (sourceIcon.sameAs(replacedIcon.icon)) { 317 return true; 318 } 319 } 320 return false; 321 } 322 323 @Override onViewRemoved(View child)324 public void onViewRemoved(View child) { 325 super.onViewRemoved(child); 326 327 if (child instanceof StatusBarIconView) { 328 boolean isReplacingIcon = isReplacingIcon(child); 329 final StatusBarIconView icon = (StatusBarIconView) child; 330 if (areAnimationsEnabled(icon) && icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 331 && child.getVisibility() == VISIBLE && isReplacingIcon) { 332 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX()); 333 if (mAddAnimationStartIndex < 0) { 334 mAddAnimationStartIndex = animationStartIndex; 335 } else { 336 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex); 337 } 338 } 339 if (!mChangingViewPositions) { 340 mIconStates.remove(child); 341 if (areAnimationsEnabled(icon) && !isReplacingIcon) { 342 addTransientView(icon, 0); 343 boolean isIsolatedIcon = child == mIsolatedIcon; 344 icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */, 345 () -> removeTransientView(icon), 346 isIsolatedIcon ? CONTENT_FADE_DURATION : 0); 347 } 348 } 349 } 350 } 351 areAnimationsEnabled(StatusBarIconView icon)352 private boolean areAnimationsEnabled(StatusBarIconView icon) { 353 return mAnimationsEnabled || icon == mIsolatedIcon; 354 } 355 356 /** 357 * Finds the first view with a translation bigger then a given value 358 */ findFirstViewIndexAfter(float translationX)359 private int findFirstViewIndexAfter(float translationX) { 360 for (int i = 0; i < getChildCount(); i++) { 361 View view = getChildAt(i); 362 if (view.getTranslationX() > translationX) { 363 return i; 364 } 365 } 366 return getChildCount(); 367 } 368 resetViewStates()369 public void resetViewStates() { 370 for (int i = 0; i < getChildCount(); i++) { 371 View view = getChildAt(i); 372 ViewState iconState = mIconStates.get(view); 373 iconState.initFrom(view); 374 iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f; 375 iconState.hidden = false; 376 } 377 } 378 379 /** 380 * Calculate the horizontal translations for each notification based on how much the icons 381 * are inserted into the notification container. 382 * If this is not a whole number, the fraction means by how much the icon is appearing. 383 */ calculateIconTranslations()384 public void calculateIconTranslations() { 385 float translationX = getActualPaddingStart(); 386 int firstOverflowIndex = -1; 387 int childCount = getChildCount(); 388 int maxVisibleIcons = mOnLockScreen ? MAX_VISIBLE_ICONS_ON_LOCK : 389 mIsStaticLayout ? MAX_STATIC_ICONS : childCount; 390 float layoutEnd = getLayoutEnd(); 391 float overflowStart = getMaxOverflowStart(); 392 mVisualOverflowStart = 0; 393 mFirstVisibleIconState = null; 394 boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount(); 395 for (int i = 0; i < childCount; i++) { 396 View view = getChildAt(i); 397 IconState iconState = mIconStates.get(view); 398 if (iconState.iconAppearAmount == 1.0f) { 399 // We only modify the xTranslation if it's fully inside of the container 400 // since during the transition to the shelf, the translations are controlled 401 // from the outside 402 iconState.xTranslation = translationX; 403 } 404 if (mFirstVisibleIconState == null) { 405 mFirstVisibleIconState = iconState; 406 } 407 boolean forceOverflow = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex 408 && iconState.iconAppearAmount > 0.0f || i >= maxVisibleIcons; 409 boolean noOverflowAfter = i == childCount - 1; 410 float drawingScale = mOnLockScreen && view instanceof StatusBarIconView 411 ? ((StatusBarIconView) view).getIconScaleIncreased() 412 : 1f; 413 if (mOpenedAmount != 0.0f) { 414 noOverflowAfter = noOverflowAfter && !hasAmbient && !forceOverflow; 415 } 416 iconState.visibleState = StatusBarIconView.STATE_ICON; 417 418 boolean isOverflowing = 419 (translationX > (noOverflowAfter ? layoutEnd - mIconSize 420 : overflowStart - mIconSize)); 421 if (firstOverflowIndex == -1 && (forceOverflow || isOverflowing)) { 422 firstOverflowIndex = noOverflowAfter && !forceOverflow ? i - 1 : i; 423 mVisualOverflowStart = layoutEnd - mOverflowWidth; 424 if (forceOverflow || mIsStaticLayout) { 425 mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); 426 } 427 } 428 translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; 429 } 430 mNumDots = 0; 431 if (firstOverflowIndex != -1) { 432 translationX = mVisualOverflowStart; 433 for (int i = firstOverflowIndex; i < childCount; i++) { 434 View view = getChildAt(i); 435 IconState iconState = mIconStates.get(view); 436 int dotWidth = mStaticDotDiameter + mDotPadding; 437 iconState.xTranslation = translationX; 438 if (mNumDots < MAX_DOTS) { 439 if (mNumDots == 0 && iconState.iconAppearAmount < 0.8f) { 440 iconState.visibleState = StatusBarIconView.STATE_ICON; 441 } else { 442 iconState.visibleState = StatusBarIconView.STATE_DOT; 443 mNumDots++; 444 } 445 translationX += (mNumDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth) 446 * iconState.iconAppearAmount; 447 mLastVisibleIconState = iconState; 448 } else { 449 iconState.visibleState = StatusBarIconView.STATE_HIDDEN; 450 } 451 } 452 } else if (childCount > 0) { 453 View lastChild = getChildAt(childCount - 1); 454 mLastVisibleIconState = mIconStates.get(lastChild); 455 mFirstVisibleIconState = mIconStates.get(getChildAt(0)); 456 } 457 458 boolean center = mOnLockScreen; 459 if (center && translationX < getLayoutEnd()) { 460 float initialTranslation = 461 mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation; 462 463 float contentWidth = 0; 464 if (mLastVisibleIconState != null) { 465 contentWidth = mLastVisibleIconState.xTranslation + mIconSize; 466 contentWidth = Math.min(getWidth(), contentWidth) - initialTranslation; 467 } 468 float availableSpace = getLayoutEnd() - getActualPaddingStart(); 469 float delta = (availableSpace - contentWidth) / 2; 470 471 if (firstOverflowIndex != -1) { 472 // If we have an overflow, only count those half for centering because the dots 473 // don't have a lot of visual weight. 474 float deltaIgnoringOverflow = (getLayoutEnd() - mVisualOverflowStart) / 2; 475 delta = (deltaIgnoringOverflow + delta) / 2; 476 } 477 for (int i = 0; i < childCount; i++) { 478 View view = getChildAt(i); 479 IconState iconState = mIconStates.get(view); 480 iconState.xTranslation += delta; 481 } 482 } 483 484 if (isLayoutRtl()) { 485 for (int i = 0; i < childCount; i++) { 486 View view = getChildAt(i); 487 IconState iconState = mIconStates.get(view); 488 iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); 489 } 490 } 491 if (mIsolatedIcon != null) { 492 IconState iconState = mIconStates.get(mIsolatedIcon); 493 if (iconState != null) { 494 // Most of the time the icon isn't yet added when this is called but only happening 495 // later 496 iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0] 497 - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f; 498 iconState.visibleState = StatusBarIconView.STATE_ICON; 499 } 500 } 501 } 502 getLayoutEnd()503 private float getLayoutEnd() { 504 return getActualWidth() - getActualPaddingEnd(); 505 } 506 getActualPaddingEnd()507 private float getActualPaddingEnd() { 508 if (mActualPaddingEnd == NO_VALUE) { 509 return getPaddingEnd(); 510 } 511 return mActualPaddingEnd; 512 } 513 514 /** 515 * @return the actual startPadding of this view 516 */ getActualPaddingStart()517 public float getActualPaddingStart() { 518 if (mActualPaddingStart == NO_VALUE) { 519 return getPaddingStart(); 520 } 521 return mActualPaddingStart; 522 } 523 524 /** 525 * Sets whether the layout should always show the same number of icons. 526 * If this is true, the icon positions will be updated on layout. 527 * If this if false, the layout is managed from the outside and layouting won't trigger a 528 * repositioning of the icons. 529 */ setIsStaticLayout(boolean isStaticLayout)530 public void setIsStaticLayout(boolean isStaticLayout) { 531 mIsStaticLayout = isStaticLayout; 532 } 533 setActualLayoutWidth(int actualLayoutWidth)534 public void setActualLayoutWidth(int actualLayoutWidth) { 535 mActualLayoutWidth = actualLayoutWidth; 536 if (DEBUG) { 537 invalidate(); 538 } 539 } 540 setActualPaddingEnd(float paddingEnd)541 public void setActualPaddingEnd(float paddingEnd) { 542 mActualPaddingEnd = paddingEnd; 543 if (DEBUG) { 544 invalidate(); 545 } 546 } 547 setActualPaddingStart(float paddingStart)548 public void setActualPaddingStart(float paddingStart) { 549 mActualPaddingStart = paddingStart; 550 if (DEBUG) { 551 invalidate(); 552 } 553 } 554 getActualWidth()555 public int getActualWidth() { 556 if (mActualLayoutWidth == NO_VALUE) { 557 return getWidth(); 558 } 559 return mActualLayoutWidth; 560 } 561 getFinalTranslationX()562 public int getFinalTranslationX() { 563 if (mLastVisibleIconState == null) { 564 return 0; 565 } 566 567 int translation = (int) (isLayoutRtl() ? getWidth() - mLastVisibleIconState.xTranslation 568 : mLastVisibleIconState.xTranslation + mIconSize); 569 // There's a chance that last translation goes beyond the edge maybe 570 return Math.min(getWidth(), translation); 571 } 572 getMaxOverflowStart()573 private float getMaxOverflowStart() { 574 return getLayoutEnd() - mOverflowWidth; 575 } 576 setChangingViewPositions(boolean changingViewPositions)577 public void setChangingViewPositions(boolean changingViewPositions) { 578 mChangingViewPositions = changingViewPositions; 579 } 580 setDozing(boolean dozing, boolean fade, long delay)581 public void setDozing(boolean dozing, boolean fade, long delay) { 582 mDozing = dozing; 583 mDisallowNextAnimation |= !fade; 584 for (int i = 0; i < getChildCount(); i++) { 585 View view = getChildAt(i); 586 if (view instanceof StatusBarIconView) { 587 ((StatusBarIconView) view).setDozing(dozing, fade, delay); 588 } 589 } 590 } 591 getIconState(StatusBarIconView icon)592 public IconState getIconState(StatusBarIconView icon) { 593 return mIconStates.get(icon); 594 } 595 setSpeedBumpIndex(int speedBumpIndex)596 public void setSpeedBumpIndex(int speedBumpIndex) { 597 mSpeedBumpIndex = speedBumpIndex; 598 } 599 setOpenedAmount(float expandAmount)600 public void setOpenedAmount(float expandAmount) { 601 mOpenedAmount = expandAmount; 602 } 603 hasOverflow()604 public boolean hasOverflow() { 605 return mNumDots > 0; 606 } 607 608 /** 609 * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then 610 * extra padding will have to be accounted for 611 * 612 * This method has no meaning for non-static containers 613 */ hasPartialOverflow()614 public boolean hasPartialOverflow() { 615 return mNumDots > 0 && mNumDots < MAX_DOTS; 616 } 617 618 /** 619 * Get padding that can account for extra dots up to the max. The only valid values for 620 * this method are for 1 or 2 dots. 621 * @return only extraDotPadding or extraDotPadding * 2 622 */ getPartialOverflowExtraPadding()623 public int getPartialOverflowExtraPadding() { 624 if (!hasPartialOverflow()) { 625 return 0; 626 } 627 628 int partialOverflowAmount = (MAX_DOTS - mNumDots) * (mStaticDotDiameter + mDotPadding); 629 630 int adjustedWidth = getFinalTranslationX() + partialOverflowAmount; 631 // In case we actually give too much padding... 632 if (adjustedWidth > getWidth()) { 633 partialOverflowAmount = getWidth() - getFinalTranslationX(); 634 } 635 636 return partialOverflowAmount; 637 } 638 639 // Give some extra room for btw notifications if we can getNoOverflowExtraPadding()640 public int getNoOverflowExtraPadding() { 641 if (mNumDots != 0) { 642 return 0; 643 } 644 645 int collapsedPadding = mOverflowWidth; 646 647 if (collapsedPadding + getFinalTranslationX() > getWidth()) { 648 collapsedPadding = getWidth() - getFinalTranslationX(); 649 } 650 651 return collapsedPadding; 652 } 653 getIconSize()654 public int getIconSize() { 655 return mIconSize; 656 } 657 setAnimationsEnabled(boolean enabled)658 public void setAnimationsEnabled(boolean enabled) { 659 if (!enabled && mAnimationsEnabled) { 660 for (int i = 0; i < getChildCount(); i++) { 661 View child = getChildAt(i); 662 ViewState childState = mIconStates.get(child); 663 if (childState != null) { 664 childState.cancelAnimations(child); 665 childState.applyToView(child); 666 } 667 } 668 } 669 mAnimationsEnabled = enabled; 670 } 671 setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons)672 public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) { 673 mReplacingIcons = replacingIcons; 674 } 675 showIconIsolated(StatusBarIconView icon, boolean animated)676 public void showIconIsolated(StatusBarIconView icon, boolean animated) { 677 if (animated) { 678 mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; 679 } 680 mIsolatedIcon = icon; 681 updateState(); 682 } 683 setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate)684 public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) { 685 mIsolatedIconLocation = isolatedIconLocation; 686 if (requireUpdate) { 687 updateState(); 688 } 689 } 690 setOnLockScreen(boolean onLockScreen)691 public void setOnLockScreen(boolean onLockScreen) { 692 mOnLockScreen = onLockScreen; 693 } 694 695 public class IconState extends ViewState { 696 public static final int NO_VALUE = NotificationIconContainer.NO_VALUE; 697 public float iconAppearAmount = 1.0f; 698 public float clampedAppearAmount = 1.0f; 699 public int visibleState; 700 public boolean justAdded = true; 701 private boolean justReplaced; 702 public boolean needsCannedAnimation; 703 public boolean useFullTransitionAmount; 704 public boolean useLinearTransitionAmount; 705 public boolean translateContent; 706 public int iconColor = StatusBarIconView.NO_COLOR; 707 public boolean noAnimations; 708 public boolean isLastExpandIcon; 709 public int customTransformHeight = NO_VALUE; 710 private final View mView; 711 712 private final Consumer<Property> mCannedAnimationEndListener; 713 IconState(View child)714 public IconState(View child) { 715 mView = child; 716 mCannedAnimationEndListener = (property) -> { 717 // If we finished animating out of the shelf 718 if (property == View.TRANSLATION_Y && iconAppearAmount == 0.0f 719 && mView.getVisibility() == VISIBLE) { 720 mView.setVisibility(INVISIBLE); 721 } 722 }; 723 } 724 725 @Override applyToView(View view)726 public void applyToView(View view) { 727 if (view instanceof StatusBarIconView) { 728 StatusBarIconView icon = (StatusBarIconView) view; 729 boolean animate = false; 730 AnimationProperties animationProperties = null; 731 boolean animationsAllowed = areAnimationsEnabled(icon) && !mDisallowNextAnimation 732 && !noAnimations; 733 if (animationsAllowed) { 734 if (justAdded || justReplaced) { 735 super.applyToView(icon); 736 if (justAdded && iconAppearAmount != 0.0f) { 737 icon.setAlpha(0.0f); 738 icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, 739 false /* animate */); 740 animationProperties = ADD_ICON_PROPERTIES; 741 animate = true; 742 } 743 } else if (visibleState != icon.getVisibleState()) { 744 animationProperties = DOT_ANIMATION_PROPERTIES; 745 animate = true; 746 } 747 if (!animate && mAddAnimationStartIndex >= 0 748 && indexOfChild(view) >= mAddAnimationStartIndex 749 && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 750 || visibleState != StatusBarIconView.STATE_HIDDEN)) { 751 animationProperties = DOT_ANIMATION_PROPERTIES; 752 animate = true; 753 } 754 if (needsCannedAnimation) { 755 AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); 756 animationFilter.reset(); 757 animationFilter.combineFilter( 758 ICON_ANIMATION_PROPERTIES.getAnimationFilter()); 759 sTempProperties.resetCustomInterpolators(); 760 sTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES); 761 Interpolator interpolator; 762 if (icon.showsConversation()) { 763 interpolator = Interpolators.ICON_OVERSHOT_LESS; 764 } else { 765 interpolator = Interpolators.ICON_OVERSHOT; 766 } 767 sTempProperties.setCustomInterpolator(View.TRANSLATION_Y, interpolator); 768 sTempProperties.setAnimationEndAction(mCannedAnimationEndListener); 769 if (animationProperties != null) { 770 animationFilter.combineFilter(animationProperties.getAnimationFilter()); 771 sTempProperties.combineCustomInterpolators(animationProperties); 772 } 773 animationProperties = sTempProperties; 774 animationProperties.setDuration(CANNED_ANIMATION_DURATION); 775 animate = true; 776 mCannedAnimationStartIndex = indexOfChild(view); 777 } 778 if (!animate && mCannedAnimationStartIndex >= 0 779 && indexOfChild(view) > mCannedAnimationStartIndex 780 && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 781 || visibleState != StatusBarIconView.STATE_HIDDEN)) { 782 AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); 783 animationFilter.reset(); 784 animationFilter.animateX(); 785 sTempProperties.resetCustomInterpolators(); 786 animationProperties = sTempProperties; 787 animationProperties.setDuration(CANNED_ANIMATION_DURATION); 788 animate = true; 789 } 790 if (mIsolatedIconForAnimation != null) { 791 if (view == mIsolatedIconForAnimation) { 792 animationProperties = UNISOLATION_PROPERTY; 793 animationProperties.setDelay( 794 mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0); 795 } else { 796 animationProperties = UNISOLATION_PROPERTY_OTHERS; 797 animationProperties.setDelay( 798 mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0); 799 } 800 animate = true; 801 } 802 } 803 icon.setVisibleState(visibleState, animationsAllowed); 804 icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed); 805 if (animate) { 806 animateTo(icon, animationProperties); 807 } else { 808 super.applyToView(view); 809 } 810 boolean inShelf = iconAppearAmount == 1.0f; 811 icon.setIsInShelf(inShelf); 812 } 813 justAdded = false; 814 justReplaced = false; 815 needsCannedAnimation = false; 816 } 817 hasCustomTransformHeight()818 public boolean hasCustomTransformHeight() { 819 return isLastExpandIcon && customTransformHeight != NO_VALUE; 820 } 821 822 @Override initFrom(View view)823 public void initFrom(View view) { 824 super.initFrom(view); 825 if (view instanceof StatusBarIconView) { 826 iconColor = ((StatusBarIconView) view).getStaticDrawableColor(); 827 } 828 } 829 } 830 } 831