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