| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| package com.android.systemui.statusbar.phone; |
| |
| import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; |
| import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; |
| |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Icon; |
| import android.util.AttributeSet; |
| import android.util.MathUtils; |
| import android.util.Property; |
| import android.view.ContextThemeWrapper; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.animation.Interpolator; |
| |
| import androidx.annotation.VisibleForTesting; |
| import androidx.collection.ArrayMap; |
| |
| import com.android.app.animation.Interpolators; |
| import com.android.internal.statusbar.StatusBarIcon; |
| import com.android.settingslib.Utils; |
| import com.android.systemui.R; |
| import com.android.systemui.statusbar.StatusBarIconView; |
| import com.android.systemui.statusbar.notification.stack.AnimationFilter; |
| import com.android.systemui.statusbar.notification.stack.AnimationProperties; |
| import com.android.systemui.statusbar.notification.stack.ViewState; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.function.Consumer; |
| |
| /** |
| * A container for notification icons. It handles overflowing icons properly and positions them |
| * correctly on the screen. |
| */ |
| public class NotificationIconContainer extends ViewGroup { |
| private static final int NO_VALUE = Integer.MIN_VALUE; |
| private static final String TAG = "NotificationIconContainer"; |
| private static final boolean DEBUG = false; |
| private static final boolean DEBUG_OVERFLOW = false; |
| private static final int CANNED_ANIMATION_DURATION = 100; |
| private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| }.setDuration(200); |
| |
| private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter() |
| .animateX() |
| .animateY() |
| .animateAlpha() |
| .animateScale(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| |
| }.setDuration(CANNED_ANIMATION_DURATION); |
| |
| /** |
| * Temporary AnimationProperties to avoid unnecessary allocations. |
| */ |
| private static final AnimationProperties sTempProperties = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| }; |
| |
| private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| }.setDuration(200).setDelay(50); |
| |
| /** |
| * The animation property used for all icons that were not isolated, when the isolation ends. |
| * This just fades the alpha and doesn't affect the movement and has a delay. |
| */ |
| private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS |
| = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| }.setDuration(CONTENT_FADE_DURATION); |
| |
| /** |
| * The animation property used for the icon when its isolation ends. |
| * This animates the translation back to the right position. |
| */ |
| private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() { |
| private final AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); |
| |
| @Override |
| public AnimationFilter getAnimationFilter() { |
| return mAnimationFilter; |
| } |
| }.setDuration(CONTENT_FADE_DURATION); |
| |
| /* Maximum number of icons on AOD when also showing overflow dot. */ |
| private int mMaxIconsOnAod; |
| |
| /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ |
| private int mMaxIconsOnLockscreen; |
| /* Maximum number of icons in the status bar when also showing overflow dot. */ |
| private int mMaxStaticIcons; |
| |
| private boolean mIsStaticLayout = true; |
| private final HashMap<View, IconState> mIconStates = new HashMap<>(); |
| private int mDotPadding; |
| private int mStaticDotDiameter; |
| private int mActualLayoutWidth = NO_VALUE; |
| private float mActualPaddingEnd = NO_VALUE; |
| private float mActualPaddingStart = NO_VALUE; |
| private boolean mDozing; |
| private boolean mOnLockScreen; |
| private boolean mInNotificationIconShelf; |
| private boolean mChangingViewPositions; |
| private int mAddAnimationStartIndex = -1; |
| private int mCannedAnimationStartIndex = -1; |
| private int mSpeedBumpIndex = -1; |
| private int mIconSize; |
| private boolean mDisallowNextAnimation; |
| private boolean mAnimationsEnabled = true; |
| private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons; |
| // Keep track of the last visible icon so collapsed container can report on its location |
| private IconState mLastVisibleIconState; |
| private IconState mFirstVisibleIconState; |
| private float mVisualOverflowStart; |
| private boolean mIsShowingOverflowDot; |
| private StatusBarIconView mIsolatedIcon; |
| private Rect mIsolatedIconLocation; |
| private final int[] mAbsolutePosition = new int[2]; |
| private View mIsolatedIconForAnimation; |
| private int mThemedTextColorPrimary; |
| |
| public NotificationIconContainer(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| initResources(); |
| setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW)); |
| } |
| |
| private void initResources() { |
| mMaxIconsOnAod = getResources().getInteger(R.integer.max_notif_icons_on_aod); |
| mMaxIconsOnLockscreen = getResources().getInteger(R.integer.max_notif_icons_on_lockscreen); |
| mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons); |
| |
| mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); |
| int staticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); |
| mStaticDotDiameter = 2 * staticDotRadius; |
| |
| final Context themedContext = new ContextThemeWrapper(getContext(), |
| com.android.internal.R.style.Theme_DeviceDefault_DayNight); |
| mThemedTextColorPrimary = Utils.getColorAttr(themedContext, |
| com.android.internal.R.attr.textColorPrimary).getDefaultColor(); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| super.onDraw(canvas); |
| Paint paint = new Paint(); |
| paint.setColor(Color.RED); |
| paint.setStyle(Paint.Style.STROKE); |
| canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint); |
| |
| if (DEBUG_OVERFLOW) { |
| if (mLastVisibleIconState == null) { |
| return; |
| } |
| |
| int height = getHeight(); |
| int end = getFinalTranslationX(); |
| |
| // Visualize the "end" of the layout |
| paint.setColor(Color.BLUE); |
| canvas.drawLine(end, 0, end, height, paint); |
| |
| paint.setColor(Color.GREEN); |
| int lastIcon = (int) mLastVisibleIconState.getXTranslation(); |
| canvas.drawLine(lastIcon, 0, lastIcon, height, paint); |
| |
| if (mFirstVisibleIconState != null) { |
| int firstIcon = (int) mFirstVisibleIconState.getXTranslation(); |
| canvas.drawLine(firstIcon, 0, firstIcon, height, paint); |
| } |
| |
| paint.setColor(Color.RED); |
| canvas.drawLine(mVisualOverflowStart, 0, mVisualOverflowStart, height, paint); |
| } |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| initResources(); |
| } |
| |
| @Override |
| public boolean hasOverlappingRendering() { |
| // Does the same as "AlphaOptimizedFrameLayout". |
| return false; |
| } |
| |
| @Override |
| protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
| final int childCount = getChildCount(); |
| final int maxVisibleIcons = getMaxVisibleIcons(childCount); |
| final int width = MeasureSpec.getSize(widthMeasureSpec); |
| final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED); |
| int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd()); |
| for (int i = 0; i < childCount; i++) { |
| View child = getChildAt(i); |
| measureChild(child, childWidthSpec, heightMeasureSpec); |
| if (i <= maxVisibleIcons) { |
| totalWidth += child.getMeasuredWidth(); |
| } |
| } |
| final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec); |
| final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec); |
| setMeasuredDimension(measuredWidth, measuredHeight); |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| float centerY = getHeight() / 2.0f; |
| // we layout all our children on the left at the top |
| mIconSize = 0; |
| for (int i = 0; i < getChildCount(); i++) { |
| View child = getChildAt(i); |
| // We need to layout all children even the GONE ones, such that the heights are |
| // calculated correctly as they are used to calculate how many we can fit on the screen |
| int width = child.getMeasuredWidth(); |
| int height = child.getMeasuredHeight(); |
| int top = (int) (centerY - height / 2.0f); |
| child.layout(0, top, width, top + height); |
| if (i == 0) { |
| setIconSize(child.getWidth()); |
| } |
| } |
| getLocationOnScreen(mAbsolutePosition); |
| if (mIsStaticLayout) { |
| updateState(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "NotificationIconContainer(" |
| + "dozing=" + mDozing + " onLockScreen=" + mOnLockScreen |
| + " inNotificationIconShelf=" + mInNotificationIconShelf |
| + " speedBumpIndex=" + mSpeedBumpIndex |
| + " themedTextColorPrimary=#" + Integer.toHexString(mThemedTextColorPrimary) + ')'; |
| } |
| |
| @VisibleForTesting |
| public void setIconSize(int size) { |
| mIconSize = size; |
| } |
| |
| private void updateState() { |
| resetViewStates(); |
| calculateIconXTranslations(); |
| applyIconStates(); |
| } |
| |
| public void applyIconStates() { |
| for (int i = 0; i < getChildCount(); i++) { |
| View child = getChildAt(i); |
| ViewState childState = mIconStates.get(child); |
| if (childState != null) { |
| childState.applyToView(child); |
| } |
| } |
| mAddAnimationStartIndex = -1; |
| mCannedAnimationStartIndex = -1; |
| mDisallowNextAnimation = false; |
| mIsolatedIconForAnimation = null; |
| } |
| |
| @Override |
| public void onViewAdded(View child) { |
| super.onViewAdded(child); |
| boolean isReplacingIcon = isReplacingIcon(child); |
| if (!mChangingViewPositions) { |
| IconState v = new IconState(child); |
| if (isReplacingIcon) { |
| v.justAdded = false; |
| v.justReplaced = true; |
| } |
| mIconStates.put(child, v); |
| } |
| int childIndex = indexOfChild(child); |
| if (childIndex < getChildCount() - 1 && !isReplacingIcon |
| && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) { |
| if (mAddAnimationStartIndex < 0) { |
| mAddAnimationStartIndex = childIndex; |
| } else { |
| mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex); |
| } |
| } |
| if (child instanceof StatusBarIconView) { |
| ((StatusBarIconView) child).updateIconDimens(); |
| ((StatusBarIconView) child).setDozing(mDozing, false, 0); |
| } |
| } |
| |
| private boolean isReplacingIcon(View child) { |
| if (mReplacingIcons == null) { |
| return false; |
| } |
| if (!(child instanceof StatusBarIconView)) { |
| return false; |
| } |
| StatusBarIconView iconView = (StatusBarIconView) child; |
| Icon sourceIcon = iconView.getSourceIcon(); |
| String groupKey = iconView.getNotification().getGroupKey(); |
| ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey); |
| if (statusBarIcons != null) { |
| StatusBarIcon replacedIcon = statusBarIcons.get(0); |
| if (sourceIcon.sameAs(replacedIcon.icon)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public void onViewRemoved(View child) { |
| super.onViewRemoved(child); |
| |
| if (child instanceof StatusBarIconView) { |
| boolean isReplacingIcon = isReplacingIcon(child); |
| final StatusBarIconView icon = (StatusBarIconView) child; |
| if (areAnimationsEnabled(icon) && icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN |
| && child.getVisibility() == VISIBLE && isReplacingIcon) { |
| int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX()); |
| if (mAddAnimationStartIndex < 0) { |
| mAddAnimationStartIndex = animationStartIndex; |
| } else { |
| mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex); |
| } |
| } |
| if (!mChangingViewPositions) { |
| mIconStates.remove(child); |
| if (areAnimationsEnabled(icon) && !isReplacingIcon) { |
| addTransientView(icon, 0); |
| boolean isIsolatedIcon = child == mIsolatedIcon; |
| icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */, |
| () -> removeTransientView(icon), |
| isIsolatedIcon ? CONTENT_FADE_DURATION : 0); |
| } |
| } |
| } |
| } |
| |
| public boolean areIconsOverflowing() { |
| return mIsShowingOverflowDot; |
| } |
| |
| private boolean areAnimationsEnabled(StatusBarIconView icon) { |
| return mAnimationsEnabled || icon == mIsolatedIcon; |
| } |
| |
| /** |
| * Finds the first view with a translation bigger then a given value |
| */ |
| private int findFirstViewIndexAfter(float translationX) { |
| for (int i = 0; i < getChildCount(); i++) { |
| View view = getChildAt(i); |
| if (view.getTranslationX() > translationX) { |
| return i; |
| } |
| } |
| return getChildCount(); |
| } |
| |
| public void resetViewStates() { |
| for (int i = 0; i < getChildCount(); i++) { |
| View view = getChildAt(i); |
| ViewState iconState = mIconStates.get(view); |
| iconState.initFrom(view); |
| iconState.setAlpha(mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f); |
| iconState.hidden = false; |
| } |
| } |
| |
| /** |
| * @return Width of shelf for the given number of icons |
| */ |
| public float calculateWidthFor(float numIcons) { |
| if (numIcons == 0) { |
| return 0f; |
| } |
| final float contentWidth = |
| mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1); |
| return getActualPaddingStart() |
| + contentWidth |
| + getActualPaddingEnd(); |
| } |
| |
| @VisibleForTesting |
| boolean shouldForceOverflow(int i, int speedBumpIndex, float iconAppearAmount, |
| int maxVisibleIcons) { |
| return speedBumpIndex != -1 && i >= speedBumpIndex |
| && iconAppearAmount > 0.0f || i >= maxVisibleIcons; |
| } |
| |
| @VisibleForTesting |
| boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd, |
| float iconSize) { |
| if (isLastChild) { |
| return translationX + iconSize > layoutEnd; |
| } else { |
| // If the child is not the last child, we need to ensure that we have room for the next |
| // icon and the dot. The dot could be as large as an icon, so verify that we have room |
| // for 2 icons. |
| return translationX + iconSize * 2f > layoutEnd; |
| } |
| } |
| |
| /** |
| * Calculate the horizontal translations for each notification based on how much the icons |
| * are inserted into the notification container. |
| * If this is not a whole number, the fraction means by how much the icon is appearing. |
| */ |
| public void calculateIconXTranslations() { |
| float translationX = getActualPaddingStart(); |
| int firstOverflowIndex = -1; |
| int childCount = getChildCount(); |
| int maxVisibleIcons = getMaxVisibleIcons(childCount); |
| float layoutEnd = getLayoutEnd(); |
| mVisualOverflowStart = 0; |
| mFirstVisibleIconState = null; |
| for (int i = 0; i < childCount; i++) { |
| View view = getChildAt(i); |
| IconState iconState = mIconStates.get(view); |
| if (iconState.iconAppearAmount == 1.0f) { |
| // We only modify the xTranslation if it's fully inside of the container |
| // since during the transition to the shelf, the translations are controlled |
| // from the outside |
| iconState.setXTranslation(translationX); |
| } |
| if (mFirstVisibleIconState == null) { |
| mFirstVisibleIconState = iconState; |
| } |
| iconState.visibleState = iconState.hidden |
| ? StatusBarIconView.STATE_HIDDEN |
| : StatusBarIconView.STATE_ICON; |
| |
| final boolean forceOverflow = shouldForceOverflow(i, mSpeedBumpIndex, |
| iconState.iconAppearAmount, maxVisibleIcons); |
| final boolean isOverflowing = forceOverflow || isOverflowing( |
| /* isLastChild= */ i == childCount - 1, translationX, layoutEnd, mIconSize); |
| |
| // First icon to overflow. |
| if (firstOverflowIndex == -1 && isOverflowing) { |
| firstOverflowIndex = i; |
| mVisualOverflowStart = translationX; |
| } |
| final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView |
| ? ((StatusBarIconView) view).getIconScaleIncreased() |
| : 1f; |
| translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale; |
| } |
| mIsShowingOverflowDot = false; |
| if (firstOverflowIndex != -1) { |
| translationX = mVisualOverflowStart; |
| for (int i = firstOverflowIndex; i < childCount; i++) { |
| View view = getChildAt(i); |
| IconState iconState = mIconStates.get(view); |
| int dotWidth = mStaticDotDiameter + mDotPadding; |
| iconState.setXTranslation(translationX); |
| if (!mIsShowingOverflowDot) { |
| if (iconState.iconAppearAmount < 0.8f) { |
| iconState.visibleState = StatusBarIconView.STATE_ICON; |
| } else { |
| iconState.visibleState = StatusBarIconView.STATE_DOT; |
| mIsShowingOverflowDot = true; |
| } |
| translationX += dotWidth * iconState.iconAppearAmount; |
| mLastVisibleIconState = iconState; |
| } else { |
| iconState.visibleState = StatusBarIconView.STATE_HIDDEN; |
| } |
| } |
| } else if (childCount > 0) { |
| View lastChild = getChildAt(childCount - 1); |
| mLastVisibleIconState = mIconStates.get(lastChild); |
| mFirstVisibleIconState = mIconStates.get(getChildAt(0)); |
| } |
| if (isLayoutRtl()) { |
| for (int i = 0; i < childCount; i++) { |
| View view = getChildAt(i); |
| IconState iconState = mIconStates.get(view); |
| iconState.setXTranslation( |
| getWidth() - iconState.getXTranslation() - view.getWidth()); |
| } |
| } |
| if (mIsolatedIcon != null) { |
| IconState iconState = mIconStates.get(mIsolatedIcon); |
| if (iconState != null) { |
| // Most of the time the icon isn't yet added when this is called but only happening |
| // later. The isolated icon position left should equal to the mIsolatedIconLocation |
| // to ensure the icon be put at the center of the HUN icon placeholder, |
| // {@See HeadsUpAppearanceController#updateIsolatedIconLocation}. |
| iconState.setXTranslation(mIsolatedIconLocation.left - mAbsolutePosition[0]); |
| iconState.visibleState = StatusBarIconView.STATE_ICON; |
| } |
| } |
| } |
| |
| private int getMaxVisibleIcons(int childCount) { |
| return mOnLockScreen ? mMaxIconsOnAod : |
| mIsStaticLayout ? mMaxStaticIcons : childCount; |
| } |
| |
| private float getLayoutEnd() { |
| return getActualWidth() - getActualPaddingEnd(); |
| } |
| |
| private float getActualPaddingEnd() { |
| if (mActualPaddingEnd == NO_VALUE) { |
| return getPaddingEnd(); |
| } |
| return mActualPaddingEnd; |
| } |
| |
| /** |
| * @return the actual startPadding of this view |
| */ |
| public float getActualPaddingStart() { |
| if (mActualPaddingStart == NO_VALUE) { |
| return getPaddingStart(); |
| } |
| return mActualPaddingStart; |
| } |
| |
| /** |
| * Sets whether the layout should always show the same number of icons. |
| * If this is true, the icon positions will be updated on layout. |
| * If this if false, the layout is managed from the outside and layouting won't trigger a |
| * repositioning of the icons. |
| */ |
| public void setIsStaticLayout(boolean isStaticLayout) { |
| mIsStaticLayout = isStaticLayout; |
| } |
| |
| public void setActualLayoutWidth(int actualLayoutWidth) { |
| mActualLayoutWidth = actualLayoutWidth; |
| if (DEBUG) { |
| invalidate(); |
| } |
| } |
| |
| public void setActualPaddingEnd(float paddingEnd) { |
| mActualPaddingEnd = paddingEnd; |
| if (DEBUG) { |
| invalidate(); |
| } |
| } |
| |
| public void setActualPaddingStart(float paddingStart) { |
| mActualPaddingStart = paddingStart; |
| if (DEBUG) { |
| invalidate(); |
| } |
| } |
| |
| public int getActualWidth() { |
| if (mActualLayoutWidth == NO_VALUE) { |
| return getWidth(); |
| } |
| return mActualLayoutWidth; |
| } |
| |
| public int getFinalTranslationX() { |
| if (mLastVisibleIconState == null) { |
| return 0; |
| } |
| |
| int translation = (int) (isLayoutRtl() |
| ? getWidth() - mLastVisibleIconState.getXTranslation() |
| : mLastVisibleIconState.getXTranslation() + mIconSize); |
| |
| // There's a chance that last translation goes beyond the edge maybe |
| return Math.min(getWidth(), translation); |
| } |
| |
| public void setChangingViewPositions(boolean changingViewPositions) { |
| mChangingViewPositions = changingViewPositions; |
| } |
| |
| public void setDozing(boolean dozing, boolean fade, long delay) { |
| mDozing = dozing; |
| mDisallowNextAnimation |= !fade; |
| for (int i = 0; i < getChildCount(); i++) { |
| View view = getChildAt(i); |
| if (view instanceof StatusBarIconView) { |
| ((StatusBarIconView) view).setDozing(dozing, fade, delay); |
| } |
| } |
| } |
| |
| public IconState getIconState(StatusBarIconView icon) { |
| return mIconStates.get(icon); |
| } |
| |
| public void setSpeedBumpIndex(int speedBumpIndex) { |
| mSpeedBumpIndex = speedBumpIndex; |
| } |
| |
| public int getIconSize() { |
| return mIconSize; |
| } |
| |
| public void setAnimationsEnabled(boolean enabled) { |
| if (!enabled && mAnimationsEnabled) { |
| for (int i = 0; i < getChildCount(); i++) { |
| View child = getChildAt(i); |
| ViewState childState = mIconStates.get(child); |
| if (childState != null) { |
| childState.cancelAnimations(child); |
| childState.applyToView(child); |
| } |
| } |
| } |
| mAnimationsEnabled = enabled; |
| } |
| |
| public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) { |
| mReplacingIcons = replacingIcons; |
| } |
| |
| public void showIconIsolated(StatusBarIconView icon, boolean animated) { |
| if (animated) { |
| mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; |
| } |
| mIsolatedIcon = icon; |
| updateState(); |
| } |
| |
| public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) { |
| mIsolatedIconLocation = isolatedIconLocation; |
| if (requireUpdate) { |
| updateState(); |
| } |
| } |
| |
| /** |
| * Set whether the device is on the lockscreen and which lockscreen mode the device is |
| * configured to. Depending on these values, the layout of the AOD icons change. |
| */ |
| public void setOnLockScreen(boolean onLockScreen) { |
| mOnLockScreen = onLockScreen; |
| } |
| |
| public void setInNotificationIconShelf(boolean inShelf) { |
| mInNotificationIconShelf = inShelf; |
| } |
| |
| public class IconState extends ViewState { |
| public float iconAppearAmount = 1.0f; |
| public float clampedAppearAmount = 1.0f; |
| public int visibleState; |
| public boolean justAdded = true; |
| private boolean justReplaced; |
| public boolean needsCannedAnimation; |
| public int iconColor = StatusBarIconView.NO_COLOR; |
| public boolean noAnimations; |
| private final View mView; |
| |
| private final Consumer<Property> mCannedAnimationEndListener; |
| |
| public IconState(View child) { |
| mView = child; |
| mCannedAnimationEndListener = (property) -> { |
| // If we finished animating out of the shelf |
| if (property == View.TRANSLATION_Y && iconAppearAmount == 0.0f |
| && mView.getVisibility() == VISIBLE) { |
| mView.setVisibility(INVISIBLE); |
| } |
| }; |
| } |
| |
| @Override |
| public void applyToView(View view) { |
| if (view instanceof StatusBarIconView) { |
| StatusBarIconView icon = (StatusBarIconView) view; |
| boolean animate = false; |
| AnimationProperties animationProperties = null; |
| final boolean isLowPriorityIconChange = |
| (visibleState == StatusBarIconView.STATE_HIDDEN |
| && icon.getVisibleState() == StatusBarIconView.STATE_DOT) |
| || (visibleState == StatusBarIconView.STATE_DOT |
| && icon.getVisibleState() == StatusBarIconView.STATE_HIDDEN); |
| final boolean animationsAllowed = areAnimationsEnabled(icon) |
| && !mDisallowNextAnimation |
| && !noAnimations |
| && !isLowPriorityIconChange; |
| if (animationsAllowed) { |
| if (justAdded || justReplaced) { |
| super.applyToView(icon); |
| if (justAdded && iconAppearAmount != 0.0f) { |
| icon.setAlpha(0.0f); |
| icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, |
| false /* animate */); |
| animationProperties = ADD_ICON_PROPERTIES; |
| animate = true; |
| } |
| } else if (visibleState != icon.getVisibleState()) { |
| animationProperties = DOT_ANIMATION_PROPERTIES; |
| animate = true; |
| } |
| if (!animate && mAddAnimationStartIndex >= 0 |
| && indexOfChild(view) >= mAddAnimationStartIndex |
| && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN |
| || visibleState != StatusBarIconView.STATE_HIDDEN)) { |
| animationProperties = DOT_ANIMATION_PROPERTIES; |
| animate = true; |
| } |
| if (needsCannedAnimation) { |
| AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); |
| animationFilter.reset(); |
| animationFilter.combineFilter( |
| ICON_ANIMATION_PROPERTIES.getAnimationFilter()); |
| sTempProperties.resetCustomInterpolators(); |
| sTempProperties.combineCustomInterpolators(ICON_ANIMATION_PROPERTIES); |
| Interpolator interpolator; |
| if (icon.showsConversation()) { |
| interpolator = Interpolators.ICON_OVERSHOT_LESS; |
| } else { |
| interpolator = Interpolators.ICON_OVERSHOT; |
| } |
| sTempProperties.setCustomInterpolator(View.TRANSLATION_Y, interpolator); |
| sTempProperties.setAnimationEndAction(mCannedAnimationEndListener); |
| if (animationProperties != null) { |
| animationFilter.combineFilter(animationProperties.getAnimationFilter()); |
| sTempProperties.combineCustomInterpolators(animationProperties); |
| } |
| animationProperties = sTempProperties; |
| animationProperties.setDuration(CANNED_ANIMATION_DURATION); |
| animate = true; |
| mCannedAnimationStartIndex = indexOfChild(view); |
| } |
| if (!animate && mCannedAnimationStartIndex >= 0 |
| && indexOfChild(view) > mCannedAnimationStartIndex |
| && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN |
| || visibleState != StatusBarIconView.STATE_HIDDEN)) { |
| AnimationFilter animationFilter = sTempProperties.getAnimationFilter(); |
| animationFilter.reset(); |
| animationFilter.animateX(); |
| sTempProperties.resetCustomInterpolators(); |
| animationProperties = sTempProperties; |
| animationProperties.setDuration(CANNED_ANIMATION_DURATION); |
| animate = true; |
| } |
| if (mIsolatedIconForAnimation != null) { |
| if (view == mIsolatedIconForAnimation) { |
| animationProperties = UNISOLATION_PROPERTY; |
| animationProperties.setDelay( |
| mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0); |
| } else { |
| animationProperties = UNISOLATION_PROPERTY_OTHERS; |
| animationProperties.setDelay( |
| mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0); |
| } |
| animate = true; |
| } |
| } |
| icon.setVisibleState(visibleState, animationsAllowed); |
| icon.setIconColor(mInNotificationIconShelf ? mThemedTextColorPrimary : iconColor, |
| needsCannedAnimation && animationsAllowed); |
| if (animate) { |
| animateTo(icon, animationProperties); |
| } else { |
| super.applyToView(view); |
| } |
| sTempProperties.setAnimationEndAction(null); |
| } |
| justAdded = false; |
| justReplaced = false; |
| needsCannedAnimation = false; |
| } |
| |
| @Override |
| public void initFrom(View view) { |
| super.initFrom(view); |
| if (view instanceof StatusBarIconView) { |
| iconColor = ((StatusBarIconView) view).getStaticDrawableColor(); |
| } |
| } |
| } |
| } |