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