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