| /* |
| * 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; |
| |
| import android.content.Context; |
| import android.content.res.Configuration; |
| import android.os.SystemProperties; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| |
| import com.android.systemui.Interpolators; |
| import com.android.systemui.R; |
| import com.android.systemui.ViewInvertHelper; |
| import com.android.systemui.statusbar.notification.NotificationUtils; |
| import com.android.systemui.statusbar.phone.NotificationIconContainer; |
| import com.android.systemui.statusbar.phone.NotificationPanelView; |
| import com.android.systemui.statusbar.stack.AmbientState; |
| import com.android.systemui.statusbar.stack.AnimationProperties; |
| import com.android.systemui.statusbar.stack.ExpandableViewState; |
| import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; |
| import com.android.systemui.statusbar.stack.StackScrollState; |
| import com.android.systemui.statusbar.stack.ViewState; |
| |
| /** |
| * A notification shelf view that is placed inside the notification scroller. It manages the |
| * overflow icons that don't fit into the regular list anymore. |
| */ |
| public class NotificationShelf extends ActivatableNotificationView implements |
| View.OnLayoutChangeListener { |
| |
| public static final boolean SHOW_AMBIENT_ICONS = true; |
| private static final boolean USE_ANIMATIONS_WHEN_OPENING = |
| SystemProperties.getBoolean("debug.icon_opening_animations", true); |
| private static final boolean ICON_ANMATIONS_WHILE_SCROLLING |
| = SystemProperties.getBoolean("debug.icon_scroll_animations", true); |
| private ViewInvertHelper mViewInvertHelper; |
| private boolean mDark; |
| private NotificationIconContainer mShelfIcons; |
| private ShelfState mShelfState; |
| private int[] mTmp = new int[2]; |
| private boolean mHideBackground; |
| private int mIconAppearTopPadding; |
| private int mStatusBarHeight; |
| private int mStatusBarPaddingStart; |
| private AmbientState mAmbientState; |
| private NotificationStackScrollLayout mHostLayout; |
| private int mMaxLayoutHeight; |
| private int mPaddingBetweenElements; |
| private int mNotGoneIndex; |
| private boolean mHasItemsInStableShelf; |
| private NotificationIconContainer mCollapsedIcons; |
| private int mScrollFastThreshold; |
| private int mStatusBarState; |
| private float mMaxShelfEnd; |
| private int mRelativeOffset; |
| private boolean mInteractive; |
| private boolean mAnimationsEnabled = true; |
| |
| public NotificationShelf(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mShelfIcons = findViewById(R.id.content); |
| mShelfIcons.setClipChildren(false); |
| mShelfIcons.setClipToPadding(false); |
| |
| setClipToActualHeight(false); |
| setClipChildren(false); |
| setClipToPadding(false); |
| mShelfIcons.setShowAllIcons(false); |
| mViewInvertHelper = new ViewInvertHelper(mShelfIcons, |
| NotificationPanelView.DOZE_ANIMATION_DURATION); |
| mShelfState = new ShelfState(); |
| initDimens(); |
| } |
| |
| public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) { |
| mAmbientState = ambientState; |
| mHostLayout = hostLayout; |
| } |
| |
| private void initDimens() { |
| mIconAppearTopPadding = getResources().getDimensionPixelSize( |
| R.dimen.notification_icon_appear_padding); |
| mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height); |
| mStatusBarPaddingStart = getResources().getDimensionPixelOffset( |
| R.dimen.status_bar_padding_start); |
| mPaddingBetweenElements = getResources().getDimensionPixelSize( |
| R.dimen.notification_divider_height); |
| ViewGroup.LayoutParams layoutParams = getLayoutParams(); |
| layoutParams.height = getResources().getDimensionPixelOffset( |
| R.dimen.notification_shelf_height); |
| setLayoutParams(layoutParams); |
| int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding); |
| mShelfIcons.setPadding(padding, 0, padding, 0); |
| mScrollFastThreshold = getResources().getDimensionPixelOffset( |
| R.dimen.scroll_fast_threshold); |
| } |
| |
| @Override |
| protected void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| initDimens(); |
| } |
| |
| @Override |
| public void setDark(boolean dark, boolean fade, long delay) { |
| super.setDark(dark, fade, delay); |
| if (mDark == dark) return; |
| mDark = dark; |
| mShelfIcons.setDark(dark, fade, delay); |
| updateInteractiveness(); |
| } |
| |
| @Override |
| protected View getContentView() { |
| return mShelfIcons; |
| } |
| |
| public NotificationIconContainer getShelfIcons() { |
| return mShelfIcons; |
| } |
| |
| @Override |
| public ExpandableViewState createNewViewState(StackScrollState stackScrollState) { |
| return mShelfState; |
| } |
| |
| public void updateState(StackScrollState resultState, |
| AmbientState ambientState) { |
| View lastView = ambientState.getLastVisibleBackgroundChild(); |
| if (lastView != null) { |
| float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding() |
| + ambientState.getStackTranslation(); |
| ExpandableViewState lastViewState = resultState.getViewStateForView(lastView); |
| float viewEnd = lastViewState.yTranslation + lastViewState.height; |
| mShelfState.copyFrom(lastViewState); |
| mShelfState.height = getIntrinsicHeight(); |
| mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height, |
| getFullyClosedTranslation()); |
| mShelfState.zTranslation = ambientState.getBaseZHeight(); |
| float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation()) |
| / (getIntrinsicHeight() * 2); |
| openedAmount = Math.min(1.0f, openedAmount); |
| mShelfState.openedAmount = openedAmount; |
| mShelfState.clipTopAmount = 0; |
| mShelfState.alpha = mAmbientState.isPulsing() ? 0 : 1; |
| mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0; |
| mShelfState.shadowAlpha = 1.0f; |
| mShelfState.hideSensitive = false; |
| mShelfState.xTranslation = getTranslationX(); |
| if (mNotGoneIndex != -1) { |
| mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex); |
| } |
| mShelfState.hasItemsInStableShelf = lastViewState.inShelf; |
| mShelfState.hidden = !mAmbientState.isShadeExpanded(); |
| mShelfState.maxShelfEnd = maxShelfEnd; |
| } else { |
| mShelfState.hidden = true; |
| mShelfState.location = ExpandableViewState.LOCATION_GONE; |
| mShelfState.hasItemsInStableShelf = false; |
| } |
| } |
| |
| /** |
| * Update the shelf appearance based on the other notifications around it. This transforms |
| * the icons from the notification area into the shelf. |
| */ |
| public void updateAppearance() { |
| mShelfIcons.resetViewStates(); |
| float shelfStart = getTranslationY(); |
| float numViewsInShelf = 0.0f; |
| View lastChild = mAmbientState.getLastVisibleBackgroundChild(); |
| mNotGoneIndex = -1; |
| float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; |
| float expandAmount = 0.0f; |
| if (shelfStart >= interpolationStart) { |
| expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight(); |
| expandAmount = Math.min(1.0f, expandAmount); |
| } |
| // find the first view that doesn't overlap with the shelf |
| int notificationIndex = 0; |
| int notGoneIndex = 0; |
| int colorOfViewBeforeLast = 0; |
| boolean backgroundForceHidden = false; |
| if (mHideBackground && !mShelfState.hasItemsInStableShelf) { |
| backgroundForceHidden = true; |
| } |
| int colorTwoBefore = NO_COLOR; |
| int previousColor = NO_COLOR; |
| float transitionAmount = 0.0f; |
| float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity(); |
| boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold |
| || (mAmbientState.isExpansionChanging() |
| && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); |
| boolean scrolling = currentScrollVelocity > 0; |
| boolean expandingAnimated = mAmbientState.isExpansionChanging() |
| && !mAmbientState.isPanelTracking(); |
| int baseZHeight = mAmbientState.getBaseZHeight(); |
| while (notificationIndex < mHostLayout.getChildCount()) { |
| ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex); |
| notificationIndex++; |
| if (!(child instanceof ExpandableNotificationRow) |
| || child.getVisibility() == GONE) { |
| continue; |
| } |
| ExpandableNotificationRow row = (ExpandableNotificationRow) child; |
| float notificationClipEnd; |
| boolean aboveShelf = row.getTranslationZ() > baseZHeight; |
| boolean isLastChild = child == lastChild; |
| float rowTranslationY = row.getTranslationY(); |
| if (isLastChild || aboveShelf || backgroundForceHidden) { |
| notificationClipEnd = shelfStart + getIntrinsicHeight(); |
| } else { |
| notificationClipEnd = shelfStart - mPaddingBetweenElements; |
| float height = notificationClipEnd - rowTranslationY; |
| if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) { |
| // We want the gap to close when we reached the minimum size and only shrink |
| // before |
| notificationClipEnd = Math.min(shelfStart, |
| rowTranslationY + getNotificationMergeSize()); |
| } |
| } |
| updateNotificationClipHeight(row, notificationClipEnd); |
| float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast, |
| expandingAnimated, isLastChild); |
| numViewsInShelf += inShelfAmount; |
| int ownColorUntinted = row.getBackgroundColorWithoutTint(); |
| if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) { |
| mNotGoneIndex = notGoneIndex; |
| setTintColor(previousColor); |
| setOverrideTintColor(colorTwoBefore, transitionAmount); |
| |
| } else if (mNotGoneIndex == -1) { |
| colorTwoBefore = previousColor; |
| transitionAmount = inShelfAmount; |
| } |
| if (isLastChild && colorOfViewBeforeLast != NO_COLOR) { |
| row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount); |
| } else { |
| colorOfViewBeforeLast = ownColorUntinted; |
| row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */); |
| } |
| if (notGoneIndex != 0 || !aboveShelf) { |
| row.setAboveShelf(false); |
| } |
| notGoneIndex++; |
| previousColor = ownColorUntinted; |
| } |
| mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex()); |
| mShelfIcons.calculateIconTranslations(); |
| mShelfIcons.applyIconStates(); |
| boolean hideBackground = numViewsInShelf < 1.0f; |
| setHideBackground(hideBackground || backgroundForceHidden); |
| if (mNotGoneIndex == -1) { |
| mNotGoneIndex = notGoneIndex; |
| } |
| } |
| |
| private void updateNotificationClipHeight(ExpandableNotificationRow row, |
| float notificationClipEnd) { |
| float viewEnd = row.getTranslationY() + row.getActualHeight(); |
| if (viewEnd > notificationClipEnd |
| && (mAmbientState.isShadeExpanded() |
| || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) { |
| row.setClipBottomAmount((int) (viewEnd - notificationClipEnd)); |
| } else { |
| row.setClipBottomAmount(0); |
| } |
| } |
| |
| /** |
| * @return the icon amount how much this notification is in the shelf; |
| */ |
| private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount, |
| boolean scrolling, boolean scrollingFast, boolean expandingAnimated, |
| boolean isLastChild) { |
| // Let calculate how much the view is in the shelf |
| float viewStart = row.getTranslationY(); |
| int fullHeight = row.getActualHeight() + mPaddingBetweenElements; |
| float iconTransformDistance = getIntrinsicHeight() * 1.5f; |
| iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount); |
| if (isLastChild) { |
| fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight()); |
| iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight() |
| - getIntrinsicHeight()); |
| } |
| float viewEnd = viewStart + fullHeight; |
| float fullTransitionAmount; |
| float iconTransitionAmount; |
| float shelfStart = getTranslationY(); |
| if (viewEnd >= shelfStart && (mAmbientState.isShadeExpanded() |
| || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) { |
| if (viewStart < shelfStart) { |
| |
| float fullAmount = (shelfStart - viewStart) / fullHeight; |
| float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation( |
| fullAmount); |
| interpolatedAmount = NotificationUtils.interpolate( |
| interpolatedAmount, fullAmount, expandAmount); |
| fullTransitionAmount = 1.0f - interpolatedAmount; |
| |
| iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance; |
| iconTransitionAmount = Math.min(1.0f, iconTransitionAmount); |
| iconTransitionAmount = 1.0f - iconTransitionAmount; |
| |
| } else { |
| fullTransitionAmount = 1.0f; |
| iconTransitionAmount = 1.0f; |
| } |
| } else { |
| fullTransitionAmount = 0.0f; |
| iconTransitionAmount = 0.0f; |
| } |
| updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount, |
| iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild); |
| return fullTransitionAmount; |
| } |
| |
| private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount, |
| float fullTransitionAmount, float iconTransformDistance, boolean scrolling, |
| boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { |
| StatusBarIconView icon = row.getEntry().expandedIcon; |
| NotificationIconContainer.IconState iconState = getIconState(icon); |
| if (iconState == null) { |
| return; |
| } |
| float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f; |
| if (clampedAmount == fullTransitionAmount) { |
| iconState.noAnimations = scrollingFast || expandingAnimated; |
| iconState.useFullTransitionAmount = iconState.noAnimations |
| || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling); |
| iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING |
| && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging(); |
| iconState.translateContent = mMaxLayoutHeight - getTranslationY() |
| - getIntrinsicHeight() > 0; |
| } |
| if (scrollingFast || (expandingAnimated && iconState.useFullTransitionAmount |
| && !ViewState.isAnimatingY(icon))) { |
| iconState.cancelAnimations(icon); |
| iconState.useFullTransitionAmount = true; |
| iconState.noAnimations = true; |
| } |
| float transitionAmount; |
| if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount |
| || iconState.useLinearTransitionAmount) { |
| transitionAmount = iconTransitionAmount; |
| } else { |
| // We take the clamped position instead |
| transitionAmount = clampedAmount; |
| iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount; |
| } |
| iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING |
| || iconState.useFullTransitionAmount |
| ? fullTransitionAmount |
| : transitionAmount; |
| iconState.clampedAppearAmount = clampedAmount; |
| float contentTransformationAmount = isLastChild || iconState.translateContent |
| ? iconTransitionAmount |
| : 0.0f; |
| row.setContentTransformationAmount(contentTransformationAmount, isLastChild); |
| setIconTransformationAmount(row, transitionAmount, iconTransformDistance, |
| clampedAmount != transitionAmount, isLastChild); |
| } |
| |
| private void setIconTransformationAmount(ExpandableNotificationRow row, |
| float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation, |
| boolean isLastChild) { |
| StatusBarIconView icon = row.getEntry().expandedIcon; |
| NotificationIconContainer.IconState iconState = getIconState(icon); |
| |
| View rowIcon = row.getNotificationIcon(); |
| float notificationIconPosition = row.getTranslationY() + row.getContentTranslation(); |
| boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf(); |
| if (usingLinearInterpolation && !stayingInShelf) { |
| // If we interpolate from the notification position, this might lead to a slightly |
| // odd interpolation, since the notification position changes as well. Let's interpolate |
| // from a fixed distance. We can only do this if we don't animate and the icon is |
| // always in the interpolated positon. |
| notificationIconPosition = getTranslationY() - iconTransformDistance; |
| } |
| float notificationIconSize = 0.0f; |
| int iconTopPadding; |
| if (rowIcon != null) { |
| iconTopPadding = row.getRelativeTopPadding(rowIcon); |
| notificationIconSize = rowIcon.getHeight(); |
| } else { |
| iconTopPadding = mIconAppearTopPadding; |
| } |
| notificationIconPosition += iconTopPadding; |
| float shelfIconPosition = getTranslationY() + icon.getTop(); |
| shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f; |
| float iconYTranslation = NotificationUtils.interpolate( |
| notificationIconPosition - shelfIconPosition, |
| 0, |
| transitionAmount); |
| float shelfIconSize = icon.getHeight() * icon.getIconScale(); |
| float alpha = 1.0f; |
| boolean noIcon = !row.isShowingIcon(); |
| if (noIcon) { |
| // The view currently doesn't have an icon, lets transform it in! |
| alpha = transitionAmount; |
| notificationIconSize = shelfIconSize / 2.0f; |
| } |
| // The notification size is different from the size in the shelf / statusbar |
| float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize, |
| transitionAmount); |
| if (iconState != null) { |
| iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale(); |
| iconState.scaleY = iconState.scaleX; |
| iconState.hidden = transitionAmount == 0.0f; |
| iconState.alpha = alpha; |
| iconState.yTranslation = iconYTranslation; |
| if (stayingInShelf) { |
| iconState.iconAppearAmount = 1.0f; |
| iconState.alpha = 1.0f; |
| iconState.scaleX = 1.0f; |
| iconState.scaleY = 1.0f; |
| iconState.hidden = false; |
| } |
| if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed() |
| || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) { |
| iconState.hidden = true; |
| } |
| int shelfColor = icon.getStaticDrawableColor(); |
| if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) { |
| int notificationColor |
| = row.getVisibleNotificationHeader().getOriginalNotificationColor(); |
| shelfColor = NotificationUtils.interpolateColors(notificationColor, shelfColor, |
| iconState.iconAppearAmount); |
| } |
| iconState.iconColor = shelfColor; |
| } |
| } |
| |
| private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) { |
| return mShelfIcons.getIconState(icon); |
| } |
| |
| private float getFullyClosedTranslation() { |
| return - (getIntrinsicHeight() - mStatusBarHeight) / 2; |
| } |
| |
| public int getNotificationMergeSize() { |
| return getIntrinsicHeight(); |
| } |
| |
| @Override |
| public boolean hasNoContentHeight() { |
| return true; |
| } |
| |
| private void setHideBackground(boolean hideBackground) { |
| if (mHideBackground != hideBackground) { |
| mHideBackground = hideBackground; |
| updateBackground(); |
| updateOutline(); |
| } |
| } |
| |
| public boolean hidesBackground() { |
| return mHideBackground; |
| } |
| |
| @Override |
| protected boolean needsOutline() { |
| return !mHideBackground && super.needsOutline(); |
| } |
| |
| @Override |
| protected boolean shouldHideBackground() { |
| return super.shouldHideBackground() || mHideBackground; |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| super.onLayout(changed, left, top, right, bottom); |
| updateRelativeOffset(); |
| } |
| |
| private void updateRelativeOffset() { |
| mCollapsedIcons.getLocationOnScreen(mTmp); |
| mRelativeOffset = mTmp[0]; |
| getLocationOnScreen(mTmp); |
| mRelativeOffset -= mTmp[0]; |
| } |
| |
| private void setOpenedAmount(float openedAmount) { |
| if (!mAmbientState.isPanelFullWidth()) { |
| // We don't do a transformation at all, lets just assume we are fully opened |
| openedAmount = 1.0f; |
| } |
| int start = mRelativeOffset; |
| if (isLayoutRtl()) { |
| start = getWidth() - start - mCollapsedIcons.getWidth(); |
| } |
| int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(), |
| mShelfIcons.getWidth(), |
| openedAmount); |
| mShelfIcons.setActualLayoutWidth(width); |
| boolean hasOverflow = mCollapsedIcons.hasOverflow(); |
| int collapsedPadding = mCollapsedIcons.getPaddingEnd(); |
| if (!hasOverflow) { |
| // we have to ensure that adding the low priority notification won't lead to an |
| // overflow |
| collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT) |
| * mCollapsedIcons.getIconSize(); |
| } |
| float padding = NotificationUtils.interpolate(collapsedPadding, |
| mShelfIcons.getPaddingEnd(), |
| openedAmount); |
| mShelfIcons.setActualPaddingEnd(padding); |
| float paddingStart = NotificationUtils.interpolate(start, |
| mShelfIcons.getPaddingStart(), openedAmount); |
| mShelfIcons.setActualPaddingStart(paddingStart); |
| mShelfIcons.setOpenedAmount(openedAmount); |
| mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption()); |
| } |
| |
| public void setMaxLayoutHeight(int maxLayoutHeight) { |
| mMaxLayoutHeight = maxLayoutHeight; |
| } |
| |
| /** |
| * @return the index of the notification at which the shelf visually resides |
| */ |
| public int getNotGoneIndex() { |
| return mNotGoneIndex; |
| } |
| |
| private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) { |
| if (mHasItemsInStableShelf != hasItemsInStableShelf) { |
| mHasItemsInStableShelf = hasItemsInStableShelf; |
| updateInteractiveness(); |
| } |
| } |
| |
| /** |
| * @return whether the shelf has any icons in it when a potential animation has finished, i.e |
| * if the current state would be applied right now |
| */ |
| public boolean hasItemsInStableShelf() { |
| return mHasItemsInStableShelf; |
| } |
| |
| public void setCollapsedIcons(NotificationIconContainer collapsedIcons) { |
| mCollapsedIcons = collapsedIcons; |
| mCollapsedIcons.addOnLayoutChangeListener(this); |
| } |
| |
| public void setStatusBarState(int statusBarState) { |
| if (mStatusBarState != statusBarState) { |
| mStatusBarState = statusBarState; |
| updateInteractiveness(); |
| } |
| } |
| |
| private void updateInteractiveness() { |
| mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf |
| && !mDark; |
| setClickable(mInteractive); |
| setFocusable(mInteractive); |
| setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES |
| : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); |
| } |
| |
| @Override |
| protected boolean isInteractive() { |
| return mInteractive; |
| } |
| |
| public void setMaxShelfEnd(float maxShelfEnd) { |
| mMaxShelfEnd = maxShelfEnd; |
| } |
| |
| public void setAnimationsEnabled(boolean enabled) { |
| mAnimationsEnabled = enabled; |
| mCollapsedIcons.setAnimationsEnabled(enabled); |
| if (!enabled) { |
| // we need to wait with enabling the animations until the first frame has passed |
| mShelfIcons.setAnimationsEnabled(false); |
| } |
| } |
| |
| @Override |
| public boolean hasOverlappingRendering() { |
| return false; // Shelf only uses alpha for transitions where the difference can't be seen. |
| } |
| |
| @Override |
| public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { |
| super.onInitializeAccessibilityNodeInfo(info); |
| if (mInteractive) { |
| info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); |
| AccessibilityNodeInfo.AccessibilityAction unlock |
| = new AccessibilityNodeInfo.AccessibilityAction( |
| AccessibilityNodeInfo.ACTION_CLICK, |
| getContext().getString(R.string.accessibility_overflow_action)); |
| info.addAction(unlock); |
| } |
| } |
| |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, |
| int oldTop, int oldRight, int oldBottom) { |
| updateRelativeOffset(); |
| } |
| |
| private class ShelfState extends ExpandableViewState { |
| private float openedAmount; |
| private boolean hasItemsInStableShelf; |
| private float maxShelfEnd; |
| |
| @Override |
| public void applyToView(View view) { |
| super.applyToView(view); |
| setMaxShelfEnd(maxShelfEnd); |
| setOpenedAmount(openedAmount); |
| updateAppearance(); |
| setHasItemsInStableShelf(hasItemsInStableShelf); |
| mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); |
| } |
| |
| @Override |
| public void animateTo(View child, AnimationProperties properties) { |
| super.animateTo(child, properties); |
| setMaxShelfEnd(maxShelfEnd); |
| setOpenedAmount(openedAmount); |
| updateAppearance(); |
| setHasItemsInStableShelf(hasItemsInStableShelf); |
| mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); |
| } |
| } |
| } |