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