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