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.internal.statusbar.StatusBarIcon; 40 import com.android.settingslib.Utils; 41 import com.android.systemui.R; 42 import com.android.systemui.animation.Interpolators; 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 /** 58 * A float value indicating how much before the overflow start the icons should transform into 59 * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts 60 * 1 icon width early. 61 */ 62 public static final float OVERFLOW_EARLY_AMOUNT = 0.2f; 63 private static final int NO_VALUE = Integer.MIN_VALUE; 64 private static final String TAG = "NotificationIconContainer"; 65 private static final boolean DEBUG = false; 66 private static final boolean DEBUG_OVERFLOW = false; 67 private static final int CANNED_ANIMATION_DURATION = 100; 68 private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { 69 private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); 70 71 @Override 72 public AnimationFilter getAnimationFilter() { 73 return mAnimationFilter; 74 } 75 }.setDuration(200); 76 77 private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() { 78 private AnimationFilter mAnimationFilter = new AnimationFilter() 79 .animateX() 80 .animateY() 81 .animateAlpha() 82 .animateScale(); 83 84 @Override 85 public AnimationFilter getAnimationFilter() { 86 return mAnimationFilter; 87 } 88 89 }.setDuration(CANNED_ANIMATION_DURATION); 90 91 /** 92 * Temporary AnimationProperties to avoid unnecessary allocations. 93 */ 94 private static final AnimationProperties sTempProperties = new AnimationProperties() { 95 private AnimationFilter mAnimationFilter = new AnimationFilter(); 96 97 @Override 98 public AnimationFilter getAnimationFilter() { 99 return mAnimationFilter; 100 } 101 }; 102 103 private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() { 104 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); 105 106 @Override 107 public AnimationFilter getAnimationFilter() { 108 return mAnimationFilter; 109 } 110 }.setDuration(200).setDelay(50); 111 112 /** 113 * The animation property used for all icons that were not isolated, when the isolation ends. 114 * This just fades the alpha and doesn't affect the movement and has a delay. 115 */ 116 private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS 117 = new AnimationProperties() { 118 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); 119 120 @Override 121 public AnimationFilter getAnimationFilter() { 122 return mAnimationFilter; 123 } 124 }.setDuration(CONTENT_FADE_DURATION); 125 126 /** 127 * The animation property used for the icon when its isolation ends. 128 * This animates the translation back to the right position. 129 */ 130 private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() { 131 private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); 132 133 @Override 134 public AnimationFilter getAnimationFilter() { 135 return mAnimationFilter; 136 } 137 }.setDuration(CONTENT_FADE_DURATION); 138 139 private static final int MAX_ICONS_ON_AOD = 3; 140 141 /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ 142 public static final int MAX_ICONS_ON_LOCKSCREEN = 3; 143 public static final int MAX_STATIC_ICONS = 4; 144 145 private boolean mIsStaticLayout = true; 146 private final HashMap<View, IconState> mIconStates = new HashMap<>(); 147 private int mDotPadding; 148 private int mStaticDotRadius; 149 private int mStaticDotDiameter; 150 private int mActualLayoutWidth = NO_VALUE; 151 private float mActualPaddingEnd = NO_VALUE; 152 private float mActualPaddingStart = NO_VALUE; 153 private boolean mDozing; 154 private boolean mOnLockScreen; 155 private boolean mInNotificationIconShelf; 156 private boolean mChangingViewPositions; 157 private int mAddAnimationStartIndex = -1; 158 private int mCannedAnimationStartIndex = -1; 159 private int mSpeedBumpIndex = -1; 160 private int mIconSize; 161 private boolean mDisallowNextAnimation; 162 private boolean mAnimationsEnabled = true; 163 private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; 164 // Keep track of the last visible icon so collapsed container can report on its location 165 private IconState mLastVisibleIconState; 166 private IconState mFirstVisibleIconState; 167 private float mVisualOverflowStart; 168 private boolean mIsShowingOverflowDot; 169 private StatusBarIconView mIsolatedIcon; 170 private Rect mIsolatedIconLocation; 171 private int[] mAbsolutePosition = new int[2]; 172 private View mIsolatedIconForAnimation; 173 private int mThemedTextColorPrimary; 174 NotificationIconContainer(Context context, AttributeSet attrs)175 public NotificationIconContainer(Context context, AttributeSet attrs) { 176 super(context, attrs); 177 initDimens(); 178 setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW)); 179 } 180 initDimens()181 private void initDimens() { 182 mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); 183 mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); 184 mStaticDotDiameter = 2 * mStaticDotRadius; 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 initDimens(); 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).setDozing(mDozing, false, 0); 336 } 337 } 338 isReplacingIcon(View child)339 private boolean isReplacingIcon(View child) { 340 if (mReplacingIcons == null) { 341 return false; 342 } 343 if (!(child instanceof StatusBarIconView)) { 344 return false; 345 } 346 StatusBarIconView iconView = (StatusBarIconView) child; 347 Icon sourceIcon = iconView.getSourceIcon(); 348 String groupKey = iconView.getNotification().getGroupKey(); 349 ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey); 350 if (statusBarIcons != null) { 351 StatusBarIcon replacedIcon = statusBarIcons.get(0); 352 if (sourceIcon.sameAs(replacedIcon.icon)) { 353 return true; 354 } 355 } 356 return false; 357 } 358 359 @Override onViewRemoved(View child)360 public void onViewRemoved(View child) { 361 super.onViewRemoved(child); 362 363 if (child instanceof StatusBarIconView) { 364 boolean isReplacingIcon = isReplacingIcon(child); 365 final StatusBarIconView icon = (StatusBarIconView) child; 366 if (areAnimationsEnabled(icon) && icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 367 && child.getVisibility() == VISIBLE && isReplacingIcon) { 368 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX()); 369 if (mAddAnimationStartIndex < 0) { 370 mAddAnimationStartIndex = animationStartIndex; 371 } else { 372 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex); 373 } 374 } 375 if (!mChangingViewPositions) { 376 mIconStates.remove(child); 377 if (areAnimationsEnabled(icon) && !isReplacingIcon) { 378 addTransientView(icon, 0); 379 boolean isIsolatedIcon = child == mIsolatedIcon; 380 icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */, 381 () -> removeTransientView(icon), 382 isIsolatedIcon ? CONTENT_FADE_DURATION : 0); 383 } 384 } 385 } 386 } 387 areIconsOverflowing()388 public boolean areIconsOverflowing() { 389 return mIsShowingOverflowDot; 390 } 391 areAnimationsEnabled(StatusBarIconView icon)392 private boolean areAnimationsEnabled(StatusBarIconView icon) { 393 return mAnimationsEnabled || icon == mIsolatedIcon; 394 } 395 396 /** 397 * Finds the first view with a translation bigger then a given value 398 */ findFirstViewIndexAfter(float translationX)399 private int findFirstViewIndexAfter(float translationX) { 400 for (int i = 0; i < getChildCount(); i++) { 401 View view = getChildAt(i); 402 if (view.getTranslationX() > translationX) { 403 return i; 404 } 405 } 406 return getChildCount(); 407 } 408 resetViewStates()409 public void resetViewStates() { 410 for (int i = 0; i < getChildCount(); i++) { 411 View view = getChildAt(i); 412 ViewState iconState = mIconStates.get(view); 413 iconState.initFrom(view); 414 iconState.setAlpha(mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f); 415 iconState.hidden = false; 416 } 417 } 418 419 /** 420 * @return Width of shelf for the given number of icons 421 */ calculateWidthFor(float numIcons)422 public float calculateWidthFor(float numIcons) { 423 if (numIcons == 0) { 424 return 0f; 425 } 426 final float contentWidth = 427 mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1); 428 return getActualPaddingStart() 429 + contentWidth 430 + getActualPaddingEnd(); 431 } 432 433 @VisibleForTesting shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount, int maxVisibleIcons)434 boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount, 435 int maxVisibleIcons) { 436 return speedBumpIndex != -1 && i >= speedBumpIndex 437 && iconAppearAmount > 0.0f || i >= maxVisibleIcons; 438 } 439 440 @VisibleForTesting isOverflowing(boolean isLastChild, float translationX, float layoutEnd, float iconSize)441 boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd, 442 float iconSize) { 443 // Layout end, as used here, does not include padding end. 444 final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize; 445 return translationX >= overflowX; 446 } 447 448 /** 449 * Calculate the horizontal translations for each notification based on how much the icons 450 * are inserted into the notification container. 451 * If this is not a whole number, the fraction means by how much the icon is appearing. 452 */ calculateIconXTranslations()453 public void calculateIconXTranslations() { 454 float translationX = getActualPaddingStart(); 455 int firstOverflowIndex = -1; 456 int childCount = getChildCount(); 457 int maxVisibleIcons = getMaxVisibleIcons(childCount); 458 float layoutEnd = getLayoutEnd(); 459 mVisualOverflowStart = 0; 460 mFirstVisibleIconState = null; 461 for (int i = 0; i < childCount; i++) { 462 View view = getChildAt(i); 463 IconState iconState = mIconStates.get(view); 464 if (iconState.iconAppearAmount == 1.0f) { 465 // We only modify the xTranslation if it's fully inside of the container 466 // since during the transition to the shelf, the translations are controlled 467 // from the outside 468 iconState.setXTranslation(translationX); 469 } 470 if (mFirstVisibleIconState == null) { 471 mFirstVisibleIconState = iconState; 472 } 473 iconState.visibleState = iconState.hidden 474 ? StatusBarIconView.STATE_HIDDEN 475 : StatusBarIconView.STATE_ICON; 476 477 final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex, 478 iconState.iconAppearAmount, maxVisibleIcons); 479 final boolean isOverflowing = forceOverflow || isOverflowing( 480 /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize); 481 482 // First icon to overflow. 483 if (firstOverflowIndex == -1 && isOverflowing) { 484 firstOverflowIndex = i; 485 mVisualOverflowStart = layoutEnd - mIconSize; 486 if (forceOverflow || mIsStaticLayout) { 487 mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart); 488 } 489 } 490 final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView 491 ? ((StatusBarIconView) view).getIconScaleIncreased() 492 : 1f; 493 translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; 494 } 495 mIsShowingOverflowDot = false; 496 if (firstOverflowIndex != -1) { 497 translationX = mVisualOverflowStart; 498 for (int i = firstOverflowIndex; i < childCount; i++) { 499 View view = getChildAt(i); 500 IconState iconState = mIconStates.get(view); 501 int dotWidth = mStaticDotDiameter + mDotPadding; 502 iconState.setXTranslation(translationX); 503 if (!mIsShowingOverflowDot) { 504 if (iconState.iconAppearAmount < 0.8f) { 505 iconState.visibleState = StatusBarIconView.STATE_ICON; 506 } else { 507 iconState.visibleState = StatusBarIconView.STATE_DOT; 508 mIsShowingOverflowDot = true; 509 } 510 translationX += dotWidth * iconState.iconAppearAmount; 511 mLastVisibleIconState = iconState; 512 } else { 513 iconState.visibleState = StatusBarIconView.STATE_HIDDEN; 514 } 515 } 516 } else if (childCount > 0) { 517 View lastChild = getChildAt(childCount - 1); 518 mLastVisibleIconState = mIconStates.get(lastChild); 519 mFirstVisibleIconState = mIconStates.get(getChildAt(0)); 520 } 521 if (isLayoutRtl()) { 522 for (int i = 0; i < childCount; i++) { 523 View view = getChildAt(i); 524 IconState iconState = mIconStates.get(view); 525 iconState.setXTranslation( 526 getWidth() - iconState.getXTranslation() - view.getWidth()); 527 } 528 } 529 if (mIsolatedIcon != null) { 530 IconState iconState = mIconStates.get(mIsolatedIcon); 531 if (iconState != null) { 532 // Most of the time the icon isn't yet added when this is called but only happening 533 // later 534 iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0] 535 - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f); 536 iconState.visibleState = StatusBarIconView.STATE_ICON; 537 } 538 } 539 } 540 getMaxVisibleIcons(int childCount)541 private int getMaxVisibleIcons(int childCount) { 542 return mOnLockScreen ? MAX_ICONS_ON_AOD : 543 mIsStaticLayout ? MAX_STATIC_ICONS : childCount; 544 } 545 getLayoutEnd()546 private float getLayoutEnd() { 547 return getActualWidth() - getActualPaddingEnd(); 548 } 549 getActualPaddingEnd()550 private float getActualPaddingEnd() { 551 if (mActualPaddingEnd == NO_VALUE) { 552 return getPaddingEnd(); 553 } 554 return mActualPaddingEnd; 555 } 556 557 /** 558 * @return the actual startPadding of this view 559 */ getActualPaddingStart()560 public float getActualPaddingStart() { 561 if (mActualPaddingStart == NO_VALUE) { 562 return getPaddingStart(); 563 } 564 return mActualPaddingStart; 565 } 566 567 /** 568 * Sets whether the layout should always show the same number of icons. 569 * If this is true, the icon positions will be updated on layout. 570 * If this if false, the layout is managed from the outside and layouting won't trigger a 571 * repositioning of the icons. 572 */ setIsStaticLayout(boolean isStaticLayout)573 public void setIsStaticLayout(boolean isStaticLayout) { 574 mIsStaticLayout = isStaticLayout; 575 } 576 setActualLayoutWidth(int actualLayoutWidth)577 public void setActualLayoutWidth(int actualLayoutWidth) { 578 mActualLayoutWidth = actualLayoutWidth; 579 if (DEBUG) { 580 invalidate(); 581 } 582 } 583 setActualPaddingEnd(float paddingEnd)584 public void setActualPaddingEnd(float paddingEnd) { 585 mActualPaddingEnd = paddingEnd; 586 if (DEBUG) { 587 invalidate(); 588 } 589 } 590 setActualPaddingStart(float paddingStart)591 public void setActualPaddingStart(float paddingStart) { 592 mActualPaddingStart = paddingStart; 593 if (DEBUG) { 594 invalidate(); 595 } 596 } 597 getActualWidth()598 public int getActualWidth() { 599 if (mActualLayoutWidth == NO_VALUE) { 600 return getWidth(); 601 } 602 return mActualLayoutWidth; 603 } 604 getFinalTranslationX()605 public int getFinalTranslationX() { 606 if (mLastVisibleIconState == null) { 607 return 0; 608 } 609 610 int translation = (int) (isLayoutRtl() 611 ? getWidth() - mLastVisibleIconState.getXTranslation() 612 : mLastVisibleIconState.getXTranslation() + mIconSize); 613 614 // There's a chance that last translation goes beyond the edge maybe 615 return Math.min(getWidth(), translation); 616 } 617 setChangingViewPositions(boolean changingViewPositions)618 public void setChangingViewPositions(boolean changingViewPositions) { 619 mChangingViewPositions = changingViewPositions; 620 } 621 setDozing(boolean dozing, boolean fade, long delay)622 public void setDozing(boolean dozing, boolean fade, long delay) { 623 mDozing = dozing; 624 mDisallowNextAnimation |= !fade; 625 for (int i = 0; i < getChildCount(); i++) { 626 View view = getChildAt(i); 627 if (view instanceof StatusBarIconView) { 628 ((StatusBarIconView) view).setDozing(dozing, fade, delay); 629 } 630 } 631 } 632 getIconState(StatusBarIconView icon)633 public IconState getIconState(StatusBarIconView icon) { 634 return mIconStates.get(icon); 635 } 636 setSpeedBumpIndex(int speedBumpIndex)637 public void setSpeedBumpIndex(int speedBumpIndex) { 638 mSpeedBumpIndex = speedBumpIndex; 639 } 640 getIconSize()641 public int getIconSize() { 642 return mIconSize; 643 } 644 setAnimationsEnabled(boolean enabled)645 public void setAnimationsEnabled(boolean enabled) { 646 if (!enabled && mAnimationsEnabled) { 647 for (int i = 0; i < getChildCount(); i++) { 648 View child = getChildAt(i); 649 ViewState childState = mIconStates.get(child); 650 if (childState != null) { 651 childState.cancelAnimations(child); 652 childState.applyToView(child); 653 } 654 } 655 } 656 mAnimationsEnabled = enabled; 657 } 658 setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons)659 public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) { 660 mReplacingIcons = replacingIcons; 661 } 662 showIconIsolated(StatusBarIconView icon, boolean animated)663 public void showIconIsolated(StatusBarIconView icon, boolean animated) { 664 if (animated) { 665 mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; 666 } 667 mIsolatedIcon = icon; 668 updateState(); 669 } 670 setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate)671 public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) { 672 mIsolatedIconLocation = isolatedIconLocation; 673 if (requireUpdate) { 674 updateState(); 675 } 676 } 677 678 /** 679 * Set whether the device is on the lockscreen and which lockscreen mode the device is 680 * configured to. Depending on these values, the layout of the AOD icons change. 681 */ setOnLockScreen(boolean onLockScreen)682 public void setOnLockScreen(boolean onLockScreen) { 683 mOnLockScreen = onLockScreen; 684 } 685 setInNotificationIconShelf(boolean inShelf)686 public void setInNotificationIconShelf(boolean inShelf) { 687 mInNotificationIconShelf = inShelf; 688 } 689 690 public class IconState extends ViewState { 691 public static final int NO_VALUE = NotificationIconContainer.NO_VALUE; 692 public float iconAppearAmount = 1.0f; 693 public float clampedAppearAmount = 1.0f; 694 public int visibleState; 695 public boolean justAdded = true; 696 private boolean justReplaced; 697 public boolean needsCannedAnimation; 698 public int iconColor = StatusBarIconView.NO_COLOR; 699 public boolean noAnimations; 700 private final View mView; 701 702 private final Consumer<Property> mCannedAnimationEndListener; 703 IconState(View child)704 public IconState(View child) { 705 mView = child; 706 mCannedAnimationEndListener = (property) -> { 707 // If we finished animating out of the shelf 708 if (property == View.TRANSLATION_Y && iconAppearAmount == 0.0f 709 && mView.getVisibility() == VISIBLE) { 710 mView.setVisibility(INVISIBLE); 711 } 712 }; 713 } 714 715 @Override applyToView(View view)716 public void applyToView(View view) { 717 if (view instanceof StatusBarIconView) { 718 StatusBarIconView icon = (StatusBarIconView) view; 719 boolean animate = false; 720 AnimationProperties animationProperties = null; 721 final boolean isLowPriorityIconChange = 722 (visibleState == StatusBarIconView.STATE_HIDDEN 723 && icon.getVisibleState() == StatusBarIconView.STATE_DOT) 724 || (visibleState == StatusBarIconView.STATE_DOT 725 && icon.getVisibleState() == StatusBarIconView.STATE_HIDDEN); 726 final boolean animationsAllowed = areAnimationsEnabled(icon) 727 && !mDisallowNextAnimation 728 && !noAnimations 729 && !isLowPriorityIconChange; 730 if (animationsAllowed) { 731 if (justAdded || justReplaced) { 732 super.applyToView(icon); 733 if (justAdded && iconAppearAmount != 0.0f) { 734 icon.setAlpha(0.0f); 735 icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, 736 false /* animate */); 737 animationProperties = ADD_ICON_PROPERTIES; 738 animate = true; 739 } 740 } else if (visibleState != icon.getVisibleState()) { 741 animationProperties = DOT_ANIMATION_PROPERTIES; 742 animate = true; 743 } 744 if (!animate && mAddAnimationStartIndex >= 0 745 && indexOfChild(view) >= mAddAnimationStartIndex 746 && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 747 || visibleState != StatusBarIconView.STATE_HIDDEN)) { 748 animationProperties = DOT_ANIMATION_PROPERTIES; 749 animate = true; 750 } 751 if (needsCannedAnimation) { 752 AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); 753 animationFilter.reset(); 754 animationFilter.combineFilter( 755 ICON_ANIMATION_PROPERTIES.getAnimationFilter()); 756 sTempProperties.resetCustomInterpolators(); 757 sTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES); 758 Interpolator interpolator; 759 if (icon.showsConversation()) { 760 interpolator = Interpolators.ICON_OVERSHOT_LESS; 761 } else { 762 interpolator = Interpolators.ICON_OVERSHOT; 763 } 764 sTempProperties.setCustomInterpolator(View.TRANSLATION_Y, interpolator); 765 sTempProperties.setAnimationEndAction(mCannedAnimationEndListener); 766 if (animationProperties != null) { 767 animationFilter.combineFilter(animationProperties.getAnimationFilter()); 768 sTempProperties.combineCustomInterpolators(animationProperties); 769 } 770 animationProperties = sTempProperties; 771 animationProperties.setDuration(CANNED_ANIMATION_DURATION); 772 animate = true; 773 mCannedAnimationStartIndex = indexOfChild(view); 774 } 775 if (!animate && mCannedAnimationStartIndex >= 0 776 && indexOfChild(view) > mCannedAnimationStartIndex 777 && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN 778 || visibleState != StatusBarIconView.STATE_HIDDEN)) { 779 AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); 780 animationFilter.reset(); 781 animationFilter.animateX(); 782 sTempProperties.resetCustomInterpolators(); 783 animationProperties = sTempProperties; 784 animationProperties.setDuration(CANNED_ANIMATION_DURATION); 785 animate = true; 786 } 787 if (mIsolatedIconForAnimation != null) { 788 if (view == mIsolatedIconForAnimation) { 789 animationProperties = UNISOLATION_PROPERTY; 790 animationProperties.setDelay( 791 mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0); 792 } else { 793 animationProperties = UNISOLATION_PROPERTY_OTHERS; 794 animationProperties.setDelay( 795 mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0); 796 } 797 animate = true; 798 } 799 } 800 icon.setVisibleState(visibleState, animationsAllowed); 801 icon.setIconColor(mInNotificationIconShelf ? mThemedTextColorPrimary : iconColor, 802 needsCannedAnimation && animationsAllowed); 803 if (animate) { 804 animateTo(icon, animationProperties); 805 } else { 806 super.applyToView(view); 807 } 808 sTempProperties.setAnimationEndAction(null); 809 boolean inShelf = iconAppearAmount == 1.0f; 810 icon.setIsInShelf(inShelf); 811 } 812 justAdded = false; 813 justReplaced = false; 814 needsCannedAnimation = false; 815 } 816 817 @Override initFrom(View view)818 public void initFrom(View view) { 819 super.initFrom(view); 820 if (view instanceof StatusBarIconView) { 821 iconColor = ((StatusBarIconView) view).getStaticDrawableColor(); 822 } 823 } 824 } 825 } 826