blob: fac77689e28979ddcd6aef5df1e562fe7a3d9ed4 [file] [log] [blame]
Selim Cinek281c2022016-10-13 19:14:43 -07001/*
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
17package com.android.systemui.statusbar;
18
Selim Cinek1f624952017-06-08 19:11:50 -070019import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
Selim Cinek1f624952017-06-08 19:11:50 -070020
Selim Cinek281c2022016-10-13 19:14:43 -070021import android.content.Context;
22import android.content.res.Configuration;
Anthony Chen9e05d462017-04-07 10:10:21 -070023import android.content.res.Resources;
Selim Cinek6eaacf22017-09-07 18:53:17 -070024import android.graphics.Rect;
Selim Cinek2b549f42016-11-22 16:38:51 -080025import android.os.SystemProperties;
Selim Cinek281c2022016-10-13 19:14:43 -070026import android.util.AttributeSet;
Selim Cinekb7bafbc2017-12-21 11:33:26 -080027import android.util.Log;
Lucas Dupinb561eda2018-04-09 17:25:04 -070028import android.util.MathUtils;
Selim Cinek281c2022016-10-13 19:14:43 -070029import android.view.View;
30import android.view.ViewGroup;
Selim Cinek6eaacf22017-09-07 18:53:17 -070031import android.view.ViewTreeObserver;
Selim Cinekaca84c02017-04-05 16:28:56 -070032import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek1f624952017-06-08 19:11:50 -070033
Selim Cinek9458b192016-10-25 19:02:42 -070034import com.android.systemui.Interpolators;
Selim Cinek281c2022016-10-13 19:14:43 -070035import com.android.systemui.R;
Selim Cinek281c2022016-10-13 19:14:43 -070036import com.android.systemui.statusbar.notification.NotificationUtils;
37import com.android.systemui.statusbar.phone.NotificationIconContainer;
Selim Cinek281c2022016-10-13 19:14:43 -070038import com.android.systemui.statusbar.stack.AmbientState;
Selim Cinek0cfbef42016-11-09 19:06:36 -080039import com.android.systemui.statusbar.stack.AnimationProperties;
Selim Cinek281c2022016-10-13 19:14:43 -070040import com.android.systemui.statusbar.stack.ExpandableViewState;
Selim Cinekc383fd02016-10-21 15:31:26 -070041import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
Selim Cinek281c2022016-10-13 19:14:43 -070042import com.android.systemui.statusbar.stack.StackScrollState;
Selim Cinekd5ab6452016-12-08 16:34:00 -080043import com.android.systemui.statusbar.stack.ViewState;
Selim Cinek281c2022016-10-13 19:14:43 -070044
Selim Cinek281c2022016-10-13 19:14:43 -070045/**
46 * A notification shelf view that is placed inside the notification scroller. It manages the
47 * overflow icons that don't fit into the regular list anymore.
48 */
Selim Cinek9ef119c2017-03-01 15:13:36 -080049public class NotificationShelf extends ActivatableNotificationView implements
50 View.OnLayoutChangeListener {
Selim Cinek281c2022016-10-13 19:14:43 -070051
Selim Cinek17e1b692016-12-02 18:19:11 -080052 public static final boolean SHOW_AMBIENT_ICONS = true;
Selim Cinek2b549f42016-11-22 16:38:51 -080053 private static final boolean USE_ANIMATIONS_WHEN_OPENING =
54 SystemProperties.getBoolean("debug.icon_opening_animations", true);
Selim Cineka1d97902016-12-14 16:31:40 -080055 private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
56 = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
Selim Cinek6eaacf22017-09-07 18:53:17 -070057 private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
Selim Cinekb7bafbc2017-12-21 11:33:26 -080058 private static final String TAG = "NotificationShelf";
Lucas Dupin60661a62018-04-12 10:50:13 -070059 private static final long SHELF_IN_TRANSLATION_DURATION = 200;
Lucas Dupinb561eda2018-04-09 17:25:04 -070060
Selim Cinek281c2022016-10-13 19:14:43 -070061 private boolean mDark;
Selim Cinek49014f82016-11-04 14:55:30 -070062 private NotificationIconContainer mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -070063 private ShelfState mShelfState;
64 private int[] mTmp = new int[2];
65 private boolean mHideBackground;
66 private int mIconAppearTopPadding;
Lucas Dupinb561eda2018-04-09 17:25:04 -070067 private int mShelfAppearTranslation;
Selim Cinek48ff9b42016-11-09 19:31:51 -080068 private int mStatusBarHeight;
69 private int mStatusBarPaddingStart;
Selim Cinekc383fd02016-10-21 15:31:26 -070070 private AmbientState mAmbientState;
71 private NotificationStackScrollLayout mHostLayout;
Selim Cinek9458b192016-10-25 19:02:42 -070072 private int mMaxLayoutHeight;
Selim Cineka686b2c2016-10-26 13:58:27 -070073 private int mPaddingBetweenElements;
Selim Cinekeccb5de2016-10-28 15:04:05 -070074 private int mNotGoneIndex;
75 private boolean mHasItemsInStableShelf;
Selim Cinek49014f82016-11-04 14:55:30 -070076 private NotificationIconContainer mCollapsedIcons;
Selim Cinek727903c2016-12-06 17:28:10 -080077 private int mScrollFastThreshold;
Selim Cinekb42698f2017-07-31 17:47:45 -070078 private int mIconSize;
Selim Cinek810bcde2016-12-14 17:29:23 -080079 private int mStatusBarState;
Selim Cineka1d97902016-12-14 16:31:40 -080080 private float mMaxShelfEnd;
Selim Cinekfcff4c62016-12-27 14:26:06 +010081 private int mRelativeOffset;
Selim Cinekc6813462017-01-13 17:10:38 -080082 private boolean mInteractive;
Selim Cinek2fce3c82017-05-08 12:38:09 -070083 private float mOpenedAmount;
84 private boolean mNoAnimationsInThisFrame;
Selim Cinek09bd29d2017-02-03 15:30:28 -080085 private boolean mAnimationsEnabled = true;
Anthony Chen9e05d462017-04-07 10:10:21 -070086 private boolean mShowNotificationShelf;
Selim Cinek515b2032017-11-15 10:20:19 -080087 private float mFirstElementRoundness;
Selim Cinek143672c2018-03-23 20:04:32 -070088 private Rect mClipRect = new Rect();
Selim Cinek281c2022016-10-13 19:14:43 -070089
90 public NotificationShelf(Context context, AttributeSet attrs) {
91 super(context, attrs);
92 }
93
94 @Override
95 protected void onFinishInflate() {
96 super.onFinishInflate();
Alan Viverette51efddb2017-04-05 10:00:01 -040097 mShelfIcons = findViewById(R.id.content);
Selim Cinek49014f82016-11-04 14:55:30 -070098 mShelfIcons.setClipChildren(false);
99 mShelfIcons.setClipToPadding(false);
100
Selim Cinek281c2022016-10-13 19:14:43 -0700101 setClipToActualHeight(false);
102 setClipChildren(false);
103 setClipToPadding(false);
Evan Lairdc987fc72017-12-15 10:14:22 -0500104 mShelfIcons.setIsStaticLayout(false);
Selim Cinek281c2022016-10-13 19:14:43 -0700105 mShelfState = new ShelfState();
Selim Cinek2871bef2017-11-22 08:40:00 -0800106 setBottomRoundness(1.0f, false /* animate */);
Selim Cinek281c2022016-10-13 19:14:43 -0700107 initDimens();
108 }
109
Selim Cinekc383fd02016-10-21 15:31:26 -0700110 public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
111 mAmbientState = ambientState;
112 mHostLayout = hostLayout;
113 }
114
Selim Cinek281c2022016-10-13 19:14:43 -0700115 private void initDimens() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700116 Resources res = getResources();
117 mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
118 mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
119 mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
120 mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
Lucas Dupinb561eda2018-04-09 17:25:04 -0700121 mShelfAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation);
Anthony Chen9e05d462017-04-07 10:10:21 -0700122
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800123 ViewGroup.LayoutParams layoutParams = getLayoutParams();
Anthony Chen9e05d462017-04-07 10:10:21 -0700124 layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800125 setLayoutParams(layoutParams);
Anthony Chen9e05d462017-04-07 10:10:21 -0700126
127 int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800128 mShelfIcons.setPadding(padding, 0, padding, 0);
Anthony Chen9e05d462017-04-07 10:10:21 -0700129 mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
130 mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
Selim Cinekb42698f2017-07-31 17:47:45 -0700131 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
Anthony Chen9e05d462017-04-07 10:10:21 -0700132
133 if (!mShowNotificationShelf) {
134 setVisibility(GONE);
135 }
Selim Cinek281c2022016-10-13 19:14:43 -0700136 }
137
138 @Override
139 protected void onConfigurationChanged(Configuration newConfig) {
140 super.onConfigurationChanged(newConfig);
141 initDimens();
142 }
143
144 @Override
145 public void setDark(boolean dark, boolean fade, long delay) {
146 super.setDark(dark, fade, delay);
147 if (mDark == dark) return;
148 mDark = dark;
Adrian Roos456e0052017-04-04 16:44:29 -0700149 mShelfIcons.setDark(dark, fade, delay);
Adrian Roos03cf2582017-03-28 17:54:05 -0700150 updateInteractiveness();
Selim Cinek281c2022016-10-13 19:14:43 -0700151 }
152
Lucas Dupinb561eda2018-04-09 17:25:04 -0700153 public void fadeInTranslating() {
154 float translation = mShelfIcons.getTranslationY();
Lucas Dupin60661a62018-04-12 10:50:13 -0700155 mShelfIcons.setTranslationY(translation - mShelfAppearTranslation);
Lucas Dupinb561eda2018-04-09 17:25:04 -0700156 mShelfIcons.setAlpha(0);
157 mShelfIcons.animate()
Lucas Dupin60661a62018-04-12 10:50:13 -0700158 .setInterpolator(Interpolators.DECELERATE_QUINT)
Lucas Dupinb561eda2018-04-09 17:25:04 -0700159 .translationY(translation)
160 .setDuration(SHELF_IN_TRANSLATION_DURATION)
161 .start();
Lucas Dupin60661a62018-04-12 10:50:13 -0700162 mShelfIcons.animate()
163 .alpha(1)
164 .setInterpolator(Interpolators.LINEAR)
165 .setDuration(SHELF_IN_TRANSLATION_DURATION)
166 .start();
Lucas Dupinb561eda2018-04-09 17:25:04 -0700167 }
168
Selim Cinek281c2022016-10-13 19:14:43 -0700169 @Override
170 protected View getContentView() {
Selim Cinek49014f82016-11-04 14:55:30 -0700171 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700172 }
173
Selim Cinek49014f82016-11-04 14:55:30 -0700174 public NotificationIconContainer getShelfIcons() {
175 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700176 }
177
178 @Override
179 public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
180 return mShelfState;
181 }
182
183 public void updateState(StackScrollState resultState,
Selim Cinek281c2022016-10-13 19:14:43 -0700184 AmbientState ambientState) {
Selim Cinekdb167372016-11-17 15:41:17 -0800185 View lastView = ambientState.getLastVisibleBackgroundChild();
Anthony Chen9e05d462017-04-07 10:10:21 -0700186 if (mShowNotificationShelf && lastView != null) {
Selim Cinek281c2022016-10-13 19:14:43 -0700187 float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
188 + ambientState.getStackTranslation();
Selim Cinek281c2022016-10-13 19:14:43 -0700189 ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
190 float viewEnd = lastViewState.yTranslation + lastViewState.height;
191 mShelfState.copyFrom(lastViewState);
192 mShelfState.height = getIntrinsicHeight();
Lucas Dupinb561eda2018-04-09 17:25:04 -0700193
194 float awakenTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
Selim Cinek49014f82016-11-04 14:55:30 -0700195 getFullyClosedTranslation());
Lucas Dupinb561eda2018-04-09 17:25:04 -0700196 float darkTranslation = mAmbientState.getDarkTopPadding();
197 float yRatio = mAmbientState.hasPulsingNotifications() ?
198 0 : mAmbientState.getDarkAmount();
199 mShelfState.yTranslation = MathUtils.lerp(awakenTranslation, darkTranslation, yRatio);
Selim Cinekd127d792016-11-01 19:11:41 -0700200 mShelfState.zTranslation = ambientState.getBaseZHeight();
Selim Cinek48ff9b42016-11-09 19:31:51 -0800201 float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
202 / (getIntrinsicHeight() * 2);
203 openedAmount = Math.min(1.0f, openedAmount);
Selim Cinek49014f82016-11-04 14:55:30 -0700204 mShelfState.openedAmount = openedAmount;
Selim Cinek281c2022016-10-13 19:14:43 -0700205 mShelfState.clipTopAmount = 0;
Selim Cinekbe2c4432017-05-30 12:11:09 -0700206 mShelfState.alpha = mAmbientState.hasPulsingNotifications() ? 0 : 1;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800207 mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
Selim Cinek281c2022016-10-13 19:14:43 -0700208 mShelfState.shadowAlpha = 1.0f;
Selim Cinek281c2022016-10-13 19:14:43 -0700209 mShelfState.hideSensitive = false;
Selim Cinek5b5beb012016-11-08 18:11:58 -0800210 mShelfState.xTranslation = getTranslationX();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700211 if (mNotGoneIndex != -1) {
212 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
213 }
214 mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
Selim Cinek5cf1d052017-06-01 17:36:46 -0700215 mShelfState.hidden = !mAmbientState.isShadeExpanded()
216 || mAmbientState.isQsCustomizerShowing();
Selim Cineka1d97902016-12-14 16:31:40 -0800217 mShelfState.maxShelfEnd = maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700218 } else {
Selim Cinek281c2022016-10-13 19:14:43 -0700219 mShelfState.hidden = true;
220 mShelfState.location = ExpandableViewState.LOCATION_GONE;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700221 mShelfState.hasItemsInStableShelf = false;
Selim Cinek281c2022016-10-13 19:14:43 -0700222 }
223 }
224
Selim Cinekc383fd02016-10-21 15:31:26 -0700225 /**
226 * Update the shelf appearance based on the other notifications around it. This transforms
227 * the icons from the notification area into the shelf.
228 */
229 public void updateAppearance() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700230 // If the shelf should not be shown, then there is no need to update anything.
231 if (!mShowNotificationShelf) {
232 return;
233 }
234
Selim Cinek65d418e2016-11-29 15:42:34 -0800235 mShelfIcons.resetViewStates();
236 float shelfStart = getTranslationY();
Selim Cinekdb167372016-11-17 15:41:17 -0800237 float numViewsInShelf = 0.0f;
238 View lastChild = mAmbientState.getLastVisibleBackgroundChild();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700239 mNotGoneIndex = -1;
Selim Cinek49014f82016-11-04 14:55:30 -0700240 float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
241 float expandAmount = 0.0f;
Selim Cinek65d418e2016-11-29 15:42:34 -0800242 if (shelfStart >= interpolationStart) {
243 expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
Selim Cinek49014f82016-11-04 14:55:30 -0700244 expandAmount = Math.min(1.0f, expandAmount);
245 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700246 // find the first view that doesn't overlap with the shelf
Selim Cinekdb167372016-11-17 15:41:17 -0800247 int notGoneIndex = 0;
Selim Cinekec29d342017-05-05 18:31:49 -0700248 int colorOfViewBeforeLast = NO_COLOR;
Selim Cinekdb167372016-11-17 15:41:17 -0800249 boolean backgroundForceHidden = false;
250 if (mHideBackground && !mShelfState.hasItemsInStableShelf) {
251 backgroundForceHidden = true;
252 }
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800253 int colorTwoBefore = NO_COLOR;
254 int previousColor = NO_COLOR;
255 float transitionAmount = 0.0f;
Selim Cineka1d97902016-12-14 16:31:40 -0800256 float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
257 boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
Selim Cinekd5ab6452016-12-08 16:34:00 -0800258 || (mAmbientState.isExpansionChanging()
259 && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
Selim Cineka1d97902016-12-14 16:31:40 -0800260 boolean scrolling = currentScrollVelocity > 0;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800261 boolean expandingAnimated = mAmbientState.isExpansionChanging()
262 && !mAmbientState.isPanelTracking();
Selim Cinek65d418e2016-11-29 15:42:34 -0800263 int baseZHeight = mAmbientState.getBaseZHeight();
Selim Cinek515b2032017-11-15 10:20:19 -0800264 int backgroundTop = 0;
265 float firstElementRoundness = 0.0f;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700266
267 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
268 ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
269
Selim Cinekc383fd02016-10-21 15:31:26 -0700270 if (!(child instanceof ExpandableNotificationRow)
271 || child.getVisibility() == GONE) {
272 continue;
273 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700274
Selim Cinekc383fd02016-10-21 15:31:26 -0700275 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cineka686b2c2016-10-26 13:58:27 -0700276 float notificationClipEnd;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800277 boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight
278 || row.isPinned();
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800279 boolean isLastChild = child == lastChild;
Selim Cinek65d418e2016-11-29 15:42:34 -0800280 float rowTranslationY = row.getTranslationY();
Selim Cinek3ff9fba2017-07-20 10:47:48 -0700281 if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700282 notificationClipEnd = shelfStart + getIntrinsicHeight();
283 } else {
Selim Cineka686b2c2016-10-26 13:58:27 -0700284 notificationClipEnd = shelfStart - mPaddingBetweenElements;
Selim Cinek65d418e2016-11-29 15:42:34 -0800285 float height = notificationClipEnd - rowTranslationY;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800286 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700287 // We want the gap to close when we reached the minimum size and only shrink
288 // before
289 notificationClipEnd = Math.min(shelfStart,
Selim Cinek65d418e2016-11-29 15:42:34 -0800290 rowTranslationY + getNotificationMergeSize());
Selim Cineka686b2c2016-10-26 13:58:27 -0700291 }
292 }
293 updateNotificationClipHeight(row, notificationClipEnd);
Selim Cineka1d97902016-12-14 16:31:40 -0800294 float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast,
Selim Cinekd5ab6452016-12-08 16:34:00 -0800295 expandingAnimated, isLastChild);
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800296 numViewsInShelf += inShelfAmount;
297 int ownColorUntinted = row.getBackgroundColorWithoutTint();
Selim Cinek65d418e2016-11-29 15:42:34 -0800298 if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
Selim Cinekdb167372016-11-17 15:41:17 -0800299 mNotGoneIndex = notGoneIndex;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800300 setTintColor(previousColor);
301 setOverrideTintColor(colorTwoBefore, transitionAmount);
302
303 } else if (mNotGoneIndex == -1) {
304 colorTwoBefore = previousColor;
305 transitionAmount = inShelfAmount;
306 }
Selim Cinekec29d342017-05-05 18:31:49 -0700307 if (isLastChild) {
308 if (colorOfViewBeforeLast == NO_COLOR) {
309 colorOfViewBeforeLast = ownColorUntinted;
310 }
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800311 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
312 } else {
313 colorOfViewBeforeLast = ownColorUntinted;
314 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700315 }
Selim Cinekdb167372016-11-17 15:41:17 -0800316 if (notGoneIndex != 0 || !aboveShelf) {
Selim Cinekd127d792016-11-01 19:11:41 -0700317 row.setAboveShelf(false);
318 }
Selim Cinek0fe07392017-11-09 13:26:34 -0800319 if (notGoneIndex == 0) {
320 StatusBarIconView icon = row.getEntry().expandedIcon;
321 NotificationIconContainer.IconState iconState = getIconState(icon);
Selim Cinekb7bafbc2017-12-21 11:33:26 -0800322 if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800323 // only if the first icon is fully in the shelf we want to clip to it!
Selim Cinek515b2032017-11-15 10:20:19 -0800324 backgroundTop = (int) (row.getTranslationY() - getTranslationY());
325 firstElementRoundness = row.getCurrentTopRoundness();
Selim Cinekb7bafbc2017-12-21 11:33:26 -0800326 } else if (iconState == null) {
327 Log.wtf(TAG, "iconState is null. ExpandedIcon: " + row.getEntry().expandedIcon
328 + (row.getEntry().expandedIcon != null
329 ? "\n icon parent: " + row.getEntry().expandedIcon.getParent() : "")
330 + " \n number of notifications: " + mHostLayout.getChildCount() );
Selim Cinek0fe07392017-11-09 13:26:34 -0800331 }
332 }
Selim Cinekdb167372016-11-17 15:41:17 -0800333 notGoneIndex++;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800334 previousColor = ownColorUntinted;
Selim Cineka686b2c2016-10-26 13:58:27 -0700335 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700336
337 clipTransientViews();
338
Selim Cinek515b2032017-11-15 10:20:19 -0800339 setBackgroundTop(backgroundTop);
340 setFirstElementRoundness(firstElementRoundness);
Selim Cinek17e1b692016-12-02 18:19:11 -0800341 mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
Selim Cinek49014f82016-11-04 14:55:30 -0700342 mShelfIcons.calculateIconTranslations();
343 mShelfIcons.applyIconStates();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700344 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
345 View child = mHostLayout.getChildAt(i);
346 if (!(child instanceof ExpandableNotificationRow)
347 || child.getVisibility() == GONE) {
348 continue;
349 }
350 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
351 updateIconClipAmount(row);
352 updateContinuousClipping(row);
353 }
Selim Cinekdb167372016-11-17 15:41:17 -0800354 boolean hideBackground = numViewsInShelf < 1.0f;
355 setHideBackground(hideBackground || backgroundForceHidden);
356 if (mNotGoneIndex == -1) {
357 mNotGoneIndex = notGoneIndex;
358 }
Selim Cinek48ff9b42016-11-09 19:31:51 -0800359 }
360
Rohan Shah524cf7b2018-03-15 14:40:02 -0700361 /**
362 * Clips transient views to the top of the shelf - Transient views are only used for
363 * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
364 * don't show underneath the notification stack when something is animating and the user
365 * swipes quickly.
366 */
367 private void clipTransientViews() {
368 for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
369 View transientView = mHostLayout.getTransientView(i);
370 if (transientView instanceof ExpandableNotificationRow) {
371 ExpandableNotificationRow transientRow = (ExpandableNotificationRow) transientView;
372 updateNotificationClipHeight(transientRow, getTranslationY());
373 } else {
374 Log.e(TAG, "NotificationShelf.clipTransientViews(): "
375 + "Trying to clip non-row transient view");
376 }
377 }
378 }
379
Selim Cinek515b2032017-11-15 10:20:19 -0800380 private void setFirstElementRoundness(float firstElementRoundness) {
381 if (mFirstElementRoundness != firstElementRoundness) {
382 mFirstElementRoundness = firstElementRoundness;
383 setTopRoundness(firstElementRoundness, false /* animate */);
Selim Cinek0fe07392017-11-09 13:26:34 -0800384 }
385 }
386
Selim Cinek6eaacf22017-09-07 18:53:17 -0700387 private void updateIconClipAmount(ExpandableNotificationRow row) {
388 float maxTop = row.getTranslationY();
389 StatusBarIconView icon = row.getEntry().expandedIcon;
390 float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
Lucas Dupin16cfe452018-02-08 13:14:50 -0800391 if (shelfIconPosition < maxTop && !mAmbientState.isDark()) {
Selim Cinek6eaacf22017-09-07 18:53:17 -0700392 int top = (int) (maxTop - shelfIconPosition);
393 Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight()));
394 icon.setClipBounds(clipRect);
395 } else {
396 icon.setClipBounds(null);
397 }
398 }
399
400 private void updateContinuousClipping(final ExpandableNotificationRow row) {
401 StatusBarIconView icon = row.getEntry().expandedIcon;
Lucas Dupin16cfe452018-02-08 13:14:50 -0800402 boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDark();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700403 boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
404 if (needsContinuousClipping && !isContinuousClipping) {
Selim Cinek85845b82018-04-25 13:10:57 +0800405 final ViewTreeObserver observer = icon.getViewTreeObserver();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700406 ViewTreeObserver.OnPreDrawListener predrawListener =
407 new ViewTreeObserver.OnPreDrawListener() {
408 @Override
409 public boolean onPreDraw() {
410 boolean animatingY = ViewState.isAnimatingY(icon);
Selim Cinek85845b82018-04-25 13:10:57 +0800411 if (!animatingY) {
412 observer.removeOnPreDrawListener(this);
Selim Cinek6eaacf22017-09-07 18:53:17 -0700413 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
414 return true;
415 }
416 updateIconClipAmount(row);
417 return true;
418 }
419 };
Selim Cinek85845b82018-04-25 13:10:57 +0800420 observer.addOnPreDrawListener(predrawListener);
421 icon.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
422 @Override
423 public void onViewAttachedToWindow(View v) {
424 }
425
426 @Override
427 public void onViewDetachedFromWindow(View v) {
428 if (v == icon) {
429 observer.removeOnPreDrawListener(predrawListener);
430 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
431 }
432 }
433 });
Selim Cinek6eaacf22017-09-07 18:53:17 -0700434 icon.setTag(TAG_CONTINUOUS_CLIPPING, predrawListener);
435 }
436 }
437
Selim Cineka686b2c2016-10-26 13:58:27 -0700438 private void updateNotificationClipHeight(ExpandableNotificationRow row,
439 float notificationClipEnd) {
440 float viewEnd = row.getTranslationY() + row.getActualHeight();
Selim Cinekebf42342017-07-13 15:46:10 +0200441 boolean isPinned = (row.isPinned() || row.isHeadsUpAnimatingAway())
442 && !mAmbientState.isDozingAndNotPulsing(row);
Selim Cinekd127d792016-11-01 19:11:41 -0700443 if (viewEnd > notificationClipEnd
Selim Cinek7e0f9482017-05-22 20:00:56 -0700444 && (mAmbientState.isShadeExpanded() || !isPinned)) {
445 int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
446 if (isPinned) {
447 clipBottomAmount = Math.min(row.getIntrinsicHeight() - row.getCollapsedHeight(),
448 clipBottomAmount);
449 }
450 row.setClipBottomAmount(clipBottomAmount);
Selim Cineka686b2c2016-10-26 13:58:27 -0700451 } else {
452 row.setClipBottomAmount(0);
453 }
454 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700455
Selim Cinekc8c4cf92017-09-08 15:30:09 -0700456 @Override
457 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
458 int outlineTranslation) {
459 if (!mHasItemsInStableShelf) {
460 shadowIntensity = 0.0f;
461 }
462 super.setFakeShadowIntensity(shadowIntensity, outlineAlpha, shadowYEnd, outlineTranslation);
463 }
464
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800465 /**
466 * @return the icon amount how much this notification is in the shelf;
467 */
Selim Cinek2b549f42016-11-22 16:38:51 -0800468 private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800469 boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
470 boolean isLastChild) {
Selim Cinek1f624952017-06-08 19:11:50 -0700471 StatusBarIconView icon = row.getEntry().expandedIcon;
472 NotificationIconContainer.IconState iconState = getIconState(icon);
473 if (iconState == null) {
474 return 0.0f;
475 }
476
Selim Cinekc383fd02016-10-21 15:31:26 -0700477 // Let calculate how much the view is in the shelf
478 float viewStart = row.getTranslationY();
Selim Cinek2b549f42016-11-22 16:38:51 -0800479 int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
480 float iconTransformDistance = getIntrinsicHeight() * 1.5f;
Selim Cineka1d97902016-12-14 16:31:40 -0800481 iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
Selim Cinek1f624952017-06-08 19:11:50 -0700482 iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
Selim Cinek938bdaa2016-11-18 16:31:09 -0800483 if (isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800484 fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
Evan Laird5dad60e2018-05-31 18:48:05 -0400485 iconTransformDistance = Math.min(iconTransformDistance,
486 row.getMinHeight() - getIntrinsicHeight() * icon.getIconScale());
Selim Cinek938bdaa2016-11-18 16:31:09 -0800487 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800488 float viewEnd = viewStart + fullHeight;
Selim Cinek1f624952017-06-08 19:11:50 -0700489 if (expandingAnimated && mAmbientState.getScrollY() == 0
490 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
491 // We are expanding animated. Because we switch to a linear interpolation in this case,
492 // the last icon may be stuck in between the shelf position and the notification
493 // position, which looks pretty bad. We therefore optimize this case by applying a
494 // shorter transition such that the icon is either fully in the notification or we clamp
495 // it into the shelf if it's close enough.
496 // We need to persist this, since after the expansion, the behavior should still be the
497 // same.
498 float position = mAmbientState.getIntrinsicPadding()
499 + mHostLayout.getPositionInLinearLayout(row);
500 int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
501 if (position < maxShelfStart && position + row.getIntrinsicHeight() >= maxShelfStart
502 && row.getTranslationY() < position) {
503 iconState.isLastExpandIcon = true;
504 iconState.customTransformHeight = NO_VALUE;
505 // Let's check if we're close enough to snap into the shelf
506 boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
507 < getIntrinsicHeight();
508 if (!forceInShelf) {
509 // We are overlapping the shelf but not enough, so the icon needs to be
510 // repositioned
511 iconState.customTransformHeight = (int) (mMaxLayoutHeight
512 - getIntrinsicHeight() - position);
513 }
514 }
515 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800516 float fullTransitionAmount;
Selim Cinek01a73f92016-12-06 16:13:42 -0800517 float iconTransitionAmount;
518 float shelfStart = getTranslationY();
Selim Cinek1f624952017-06-08 19:11:50 -0700519 if (iconState.hasCustomTransformHeight()) {
520 fullHeight = iconState.customTransformHeight;
521 iconTransformDistance = iconState.customTransformHeight;
522 }
523 boolean fullyInOrOut = true;
Selim Cinekec29d342017-05-05 18:31:49 -0700524 if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
525 && (mAmbientState.isShadeExpanded()
526 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800527 if (viewStart < shelfStart) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800528 float fullAmount = (shelfStart - viewStart) / fullHeight;
Selim Cinek1f624952017-06-08 19:11:50 -0700529 fullAmount = Math.min(1.0f, fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700530 float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
Selim Cinek2b549f42016-11-22 16:38:51 -0800531 fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700532 interpolatedAmount = NotificationUtils.interpolate(
Selim Cinek2b549f42016-11-22 16:38:51 -0800533 interpolatedAmount, fullAmount, expandAmount);
534 fullTransitionAmount = 1.0f - interpolatedAmount;
535
Selim Cinek01a73f92016-12-06 16:13:42 -0800536 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
537 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
538 iconTransitionAmount = 1.0f - iconTransitionAmount;
Selim Cinek1f624952017-06-08 19:11:50 -0700539 fullyInOrOut = false;
Selim Cinekc383fd02016-10-21 15:31:26 -0700540 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800541 fullTransitionAmount = 1.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800542 iconTransitionAmount = 1.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700543 }
544 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800545 fullTransitionAmount = 0.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800546 iconTransitionAmount = 0.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700547 }
Selim Cinek1f624952017-06-08 19:11:50 -0700548 if (fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
549 iconState.isLastExpandIcon = false;
550 iconState.customTransformHeight = NO_VALUE;
551 }
Selim Cineka1d97902016-12-14 16:31:40 -0800552 updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
553 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800554 return fullTransitionAmount;
555 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700556
Selim Cinek2b549f42016-11-22 16:38:51 -0800557 private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800558 float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
559 boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800560 StatusBarIconView icon = row.getEntry().expandedIcon;
561 NotificationIconContainer.IconState iconState = getIconState(icon);
562 if (iconState == null) {
563 return;
564 }
Selim Cinek1f624952017-06-08 19:11:50 -0700565 boolean forceInShelf = iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
Selim Cinek2b549f42016-11-22 16:38:51 -0800566 float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800567 if (clampedAmount == fullTransitionAmount) {
Selim Cinek1f624952017-06-08 19:11:50 -0700568 iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
Selim Cinek44d81a62017-05-08 19:45:40 -0700569 iconState.useFullTransitionAmount = iconState.noAnimations
Selim Cineka1d97902016-12-14 16:31:40 -0800570 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
571 iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
572 && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
Selim Cinek01a73f92016-12-06 16:13:42 -0800573 iconState.translateContent = mMaxLayoutHeight - getTranslationY()
574 - getIntrinsicHeight() > 0;
Selim Cinek2b549f42016-11-22 16:38:51 -0800575 }
Selim Cinek1f624952017-06-08 19:11:50 -0700576 if (!forceInShelf && (scrollingFast || (expandingAnimated
577 && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) {
Selim Cinekd5ab6452016-12-08 16:34:00 -0800578 iconState.cancelAnimations(icon);
579 iconState.useFullTransitionAmount = true;
Selim Cinek44d81a62017-05-08 19:45:40 -0700580 iconState.noAnimations = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800581 }
Selim Cinek1f624952017-06-08 19:11:50 -0700582 if (iconState.hasCustomTransformHeight()) {
583 iconState.useFullTransitionAmount = true;
584 }
585 if (iconState.isLastExpandIcon) {
586 iconState.translateContent = false;
587 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800588 float transitionAmount;
Lucas Dupinb561eda2018-04-09 17:25:04 -0700589 if (mAmbientState.getDarkAmount() > 0 && !row.isInShelf()) {
590 transitionAmount = mAmbientState.isFullyDark() ? 1 : 0;
591 } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
Selim Cineka1d97902016-12-14 16:31:40 -0800592 || iconState.useLinearTransitionAmount) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800593 transitionAmount = iconTransitionAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800594 } else {
Selim Cineka1d97902016-12-14 16:31:40 -0800595 // We take the clamped position instead
596 transitionAmount = clampedAmount;
Selim Cinek2fce3c82017-05-08 12:38:09 -0700597 iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount
598 && !mNoAnimationsInThisFrame;
Selim Cinek2b549f42016-11-22 16:38:51 -0800599 }
600 iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
601 || iconState.useFullTransitionAmount
602 ? fullTransitionAmount
603 : transitionAmount;
604 iconState.clampedAppearAmount = clampedAmount;
Selim Cinekebf42342017-07-13 15:46:10 +0200605 float contentTransformationAmount = !mAmbientState.isAboveShelf(row)
Selim Cinek7e0f9482017-05-22 20:00:56 -0700606 && (isLastChild || iconState.translateContent)
Selim Cinek01a73f92016-12-06 16:13:42 -0800607 ? iconTransitionAmount
608 : 0.0f;
609 row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
Selim Cineka1d97902016-12-14 16:31:40 -0800610 setIconTransformationAmount(row, transitionAmount, iconTransformDistance,
Mady Mellor434180c2017-02-13 11:29:42 -0800611 clampedAmount != transitionAmount, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800612 }
613
614 private void setIconTransformationAmount(ExpandableNotificationRow row,
Mady Mellor434180c2017-02-13 11:29:42 -0800615 float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
616 boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800617 StatusBarIconView icon = row.getEntry().expandedIcon;
618 NotificationIconContainer.IconState iconState = getIconState(icon);
619
Selim Cinekc383fd02016-10-21 15:31:26 -0700620 View rowIcon = row.getNotificationIcon();
Selim Cineka1d97902016-12-14 16:31:40 -0800621 float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
Selim Cinekf20254c2017-02-03 10:09:33 -0800622 boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
623 if (usingLinearInterpolation && !stayingInShelf) {
Selim Cineka1d97902016-12-14 16:31:40 -0800624 // If we interpolate from the notification position, this might lead to a slightly
625 // odd interpolation, since the notification position changes as well. Let's interpolate
626 // from a fixed distance. We can only do this if we don't animate and the icon is
627 // always in the interpolated positon.
Selim Cinekf20254c2017-02-03 10:09:33 -0800628 notificationIconPosition = getTranslationY() - iconTransformDistance;
Selim Cineka1d97902016-12-14 16:31:40 -0800629 }
Selim Cinek281c2022016-10-13 19:14:43 -0700630 float notificationIconSize = 0.0f;
631 int iconTopPadding;
632 if (rowIcon != null) {
Selim Cinek875a3a12016-11-18 17:52:16 -0800633 iconTopPadding = row.getRelativeTopPadding(rowIcon);
Selim Cinek281c2022016-10-13 19:14:43 -0700634 notificationIconSize = rowIcon.getHeight();
635 } else {
636 iconTopPadding = mIconAppearTopPadding;
637 }
638 notificationIconPosition += iconTopPadding;
Selim Cinekc383fd02016-10-21 15:31:26 -0700639 float shelfIconPosition = getTranslationY() + icon.getTop();
Selim Cinekb42698f2017-07-31 17:47:45 -0700640 shelfIconPosition += (icon.getHeight() - icon.getIconScale() * mIconSize) / 2.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800641 float iconYTranslation = NotificationUtils.interpolate(
Selim Cineka1d97902016-12-14 16:31:40 -0800642 notificationIconPosition - shelfIconPosition,
Selim Cinek2b549f42016-11-22 16:38:51 -0800643 0,
644 transitionAmount);
Selim Cinekb42698f2017-07-31 17:47:45 -0700645 float shelfIconSize = mIconSize * icon.getIconScale();
Selim Cinek2b549f42016-11-22 16:38:51 -0800646 float alpha = 1.0f;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800647 boolean noIcon = !row.isShowingIcon();
648 if (noIcon) {
Selim Cinekc383fd02016-10-21 15:31:26 -0700649 // The view currently doesn't have an icon, lets transform it in!
Selim Cinekdb167372016-11-17 15:41:17 -0800650 alpha = transitionAmount;
Selim Cinekc383fd02016-10-21 15:31:26 -0700651 notificationIconSize = shelfIconSize / 2.0f;
652 }
653 // The notification size is different from the size in the shelf / statusbar
654 float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
Selim Cinek281c2022016-10-13 19:14:43 -0700655 transitionAmount);
Selim Cinekdb167372016-11-17 15:41:17 -0800656 if (iconState != null) {
Selim Cinekb42698f2017-07-31 17:47:45 -0700657 iconState.scaleX = newSize / shelfIconSize;
Selim Cinekdb167372016-11-17 15:41:17 -0800658 iconState.scaleY = iconState.scaleX;
Adrian Roos28f90c72017-05-08 17:24:26 -0700659 iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
Selim Cinekf38d6c32017-06-28 15:44:02 +0200660 boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
661 if (isAppearing) {
662 iconState.hidden = true;
663 iconState.iconAppearAmount = 0.0f;
664 }
Selim Cinekdb167372016-11-17 15:41:17 -0800665 iconState.alpha = alpha;
Selim Cinek2b549f42016-11-22 16:38:51 -0800666 iconState.yTranslation = iconYTranslation;
Selim Cinekf20254c2017-02-03 10:09:33 -0800667 if (stayingInShelf) {
Selim Cinekdb167372016-11-17 15:41:17 -0800668 iconState.iconAppearAmount = 1.0f;
669 iconState.alpha = 1.0f;
670 iconState.scaleX = 1.0f;
671 iconState.scaleY = 1.0f;
672 iconState.hidden = false;
673 }
Selim Cinekebf42342017-07-13 15:46:10 +0200674 if (mAmbientState.isAboveShelf(row) || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
Selim Cinek47374632017-03-17 16:07:17 -0700675 || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
Selim Cinek5ea19572016-11-29 15:34:48 -0800676 iconState.hidden = true;
677 }
Lucas Dupin83519da2017-06-21 11:58:31 -0700678 int backgroundColor = getBackgroundColorWithoutTint();
679 int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800680 if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
Lucas Dupinb6ed63b2017-05-30 16:17:42 -0700681 int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
682 shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
Selim Cinek875ba9b2017-02-13 16:20:17 -0800683 iconState.iconAppearAmount);
684 }
685 iconState.iconColor = shelfColor;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700686 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800687 }
688
689 private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
690 return mShelfIcons.getIconState(icon);
Selim Cinekc383fd02016-10-21 15:31:26 -0700691 }
692
693 private float getFullyClosedTranslation() {
694 return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek281c2022016-10-13 19:14:43 -0700695 }
696
Selim Cinek281c2022016-10-13 19:14:43 -0700697 public int getNotificationMergeSize() {
698 return getIntrinsicHeight();
699 }
700
701 @Override
702 public boolean hasNoContentHeight() {
703 return true;
704 }
Selim Cineka686b2c2016-10-26 13:58:27 -0700705
Selim Cinek281c2022016-10-13 19:14:43 -0700706 private void setHideBackground(boolean hideBackground) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800707 if (mHideBackground != hideBackground) {
708 mHideBackground = hideBackground;
709 updateBackground();
710 updateOutline();
711 }
Selim Cinekad7fac02016-10-18 17:09:15 -0700712 }
713
Selim Cinekeccb5de2016-10-28 15:04:05 -0700714 public boolean hidesBackground() {
715 return mHideBackground;
716 }
717
Selim Cinekad7fac02016-10-18 17:09:15 -0700718 @Override
719 protected boolean needsOutline() {
720 return !mHideBackground && super.needsOutline();
Selim Cinek281c2022016-10-13 19:14:43 -0700721 }
722
723 @Override
724 protected boolean shouldHideBackground() {
725 return super.shouldHideBackground() || mHideBackground;
726 }
727
Selim Cinekfcff4c62016-12-27 14:26:06 +0100728 @Override
729 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
730 super.onLayout(changed, left, top, right, bottom);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800731 updateRelativeOffset();
Selim Cinek143672c2018-03-23 20:04:32 -0700732
733 // we always want to clip to our sides, such that nothing can draw outside of these bounds
734 int height = getResources().getDisplayMetrics().heightPixels;
735 mClipRect.set(0, -height, getWidth(), height);
736 mShelfIcons.setClipBounds(mClipRect);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800737 }
738
739 private void updateRelativeOffset() {
Selim Cinek49014f82016-11-04 14:55:30 -0700740 mCollapsedIcons.getLocationOnScreen(mTmp);
Selim Cinekfcff4c62016-12-27 14:26:06 +0100741 mRelativeOffset = mTmp[0];
742 getLocationOnScreen(mTmp);
743 mRelativeOffset -= mTmp[0];
744 }
745
746 private void setOpenedAmount(float openedAmount) {
Selim Cinek2fce3c82017-05-08 12:38:09 -0700747 mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f;
748 mOpenedAmount = openedAmount;
Selim Cinekfcff4c62016-12-27 14:26:06 +0100749 if (!mAmbientState.isPanelFullWidth()) {
750 // We don't do a transformation at all, lets just assume we are fully opened
751 openedAmount = 1.0f;
752 }
753 int start = mRelativeOffset;
Selim Cinek49014f82016-11-04 14:55:30 -0700754 if (isLayoutRtl()) {
755 start = getWidth() - start - mCollapsedIcons.getWidth();
756 }
Evan Lairdc987fc72017-12-15 10:14:22 -0500757 int width = (int) NotificationUtils.interpolate(
758 start + mCollapsedIcons.getFinalTranslationX(),
Selim Cinek49014f82016-11-04 14:55:30 -0700759 mShelfIcons.getWidth(),
760 openedAmount);
761 mShelfIcons.setActualLayoutWidth(width);
Selim Cinek932005d2016-12-05 17:12:09 -0800762 boolean hasOverflow = mCollapsedIcons.hasOverflow();
763 int collapsedPadding = mCollapsedIcons.getPaddingEnd();
764 if (!hasOverflow) {
765 // we have to ensure that adding the low priority notification won't lead to an
766 // overflow
Evan Laird8cf0de42018-02-06 18:34:55 -0500767 collapsedPadding -= mCollapsedIcons.getNoOverflowExtraPadding();
Evan Lairdc987fc72017-12-15 10:14:22 -0500768 } else {
769 // Partial overflow padding will fill enough space to add extra dots
770 collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
Selim Cinek932005d2016-12-05 17:12:09 -0800771 }
772 float padding = NotificationUtils.interpolate(collapsedPadding,
Selim Cinek49014f82016-11-04 14:55:30 -0700773 mShelfIcons.getPaddingEnd(),
774 openedAmount);
775 mShelfIcons.setActualPaddingEnd(padding);
776 float paddingStart = NotificationUtils.interpolate(start,
777 mShelfIcons.getPaddingStart(), openedAmount);
778 mShelfIcons.setActualPaddingStart(paddingStart);
Selim Cinek17e1b692016-12-02 18:19:11 -0800779 mShelfIcons.setOpenedAmount(openedAmount);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800780 }
781
Selim Cinek9458b192016-10-25 19:02:42 -0700782 public void setMaxLayoutHeight(int maxLayoutHeight) {
783 mMaxLayoutHeight = maxLayoutHeight;
784 }
785
Selim Cinekeccb5de2016-10-28 15:04:05 -0700786 /**
787 * @return the index of the notification at which the shelf visually resides
788 */
789 public int getNotGoneIndex() {
790 return mNotGoneIndex;
791 }
792
793 private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
Selim Cinek810bcde2016-12-14 17:29:23 -0800794 if (mHasItemsInStableShelf != hasItemsInStableShelf) {
795 mHasItemsInStableShelf = hasItemsInStableShelf;
796 updateInteractiveness();
797 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700798 }
799
800 /**
801 * @return whether the shelf has any icons in it when a potential animation has finished, i.e
802 * if the current state would be applied right now
803 */
804 public boolean hasItemsInStableShelf() {
805 return mHasItemsInStableShelf;
806 }
807
Selim Cinek49014f82016-11-04 14:55:30 -0700808 public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
809 mCollapsedIcons = collapsedIcons;
Selim Cinek9ef119c2017-03-01 15:13:36 -0800810 mCollapsedIcons.addOnLayoutChangeListener(this);
Selim Cinek49014f82016-11-04 14:55:30 -0700811 }
812
Selim Cinek810bcde2016-12-14 17:29:23 -0800813 public void setStatusBarState(int statusBarState) {
814 if (mStatusBarState != statusBarState) {
815 mStatusBarState = statusBarState;
816 updateInteractiveness();
817 }
818 }
819
820 private void updateInteractiveness() {
Adrian Roos03cf2582017-03-28 17:54:05 -0700821 mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf
822 && !mDark;
Selim Cinekc6813462017-01-13 17:10:38 -0800823 setClickable(mInteractive);
824 setFocusable(mInteractive);
825 setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
Selim Cinekaca84c02017-04-05 16:28:56 -0700826 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Selim Cinek810bcde2016-12-14 17:29:23 -0800827 }
828
Selim Cinekc6813462017-01-13 17:10:38 -0800829 @Override
830 protected boolean isInteractive() {
831 return mInteractive;
832 }
833
Selim Cineka1d97902016-12-14 16:31:40 -0800834 public void setMaxShelfEnd(float maxShelfEnd) {
835 mMaxShelfEnd = maxShelfEnd;
836 }
837
Selim Cinek09bd29d2017-02-03 15:30:28 -0800838 public void setAnimationsEnabled(boolean enabled) {
839 mAnimationsEnabled = enabled;
840 mCollapsedIcons.setAnimationsEnabled(enabled);
841 if (!enabled) {
842 // we need to wait with enabling the animations until the first frame has passed
843 mShelfIcons.setAnimationsEnabled(false);
844 }
845 }
846
Selim Cinek9ef119c2017-03-01 15:13:36 -0800847 @Override
Adrian Roosd83e9992017-03-16 15:17:57 -0700848 public boolean hasOverlappingRendering() {
849 return false; // Shelf only uses alpha for transitions where the difference can't be seen.
850 }
851
852 @Override
Selim Cinekaca84c02017-04-05 16:28:56 -0700853 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
854 super.onInitializeAccessibilityNodeInfo(info);
855 if (mInteractive) {
856 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
857 AccessibilityNodeInfo.AccessibilityAction unlock
858 = new AccessibilityNodeInfo.AccessibilityAction(
859 AccessibilityNodeInfo.ACTION_CLICK,
860 getContext().getString(R.string.accessibility_overflow_action));
861 info.addAction(unlock);
862 }
863 }
864
865 @Override
Selim Cinek9ef119c2017-03-01 15:13:36 -0800866 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
867 int oldTop, int oldRight, int oldBottom) {
868 updateRelativeOffset();
869 }
870
Selim Cinek281c2022016-10-13 19:14:43 -0700871 private class ShelfState extends ExpandableViewState {
Selim Cinek49014f82016-11-04 14:55:30 -0700872 private float openedAmount;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700873 private boolean hasItemsInStableShelf;
Selim Cineka1d97902016-12-14 16:31:40 -0800874 private float maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700875
876 @Override
877 public void applyToView(View view) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700878 if (!mShowNotificationShelf) {
879 return;
880 }
881
Selim Cinek281c2022016-10-13 19:14:43 -0700882 super.applyToView(view);
Selim Cineka1d97902016-12-14 16:31:40 -0800883 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700884 setOpenedAmount(openedAmount);
Selim Cineka1d97902016-12-14 16:31:40 -0800885 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700886 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800887 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek281c2022016-10-13 19:14:43 -0700888 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800889
890 @Override
891 public void animateTo(View child, AnimationProperties properties) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700892 if (!mShowNotificationShelf) {
893 return;
894 }
895
Selim Cinek0cfbef42016-11-09 19:06:36 -0800896 super.animateTo(child, properties);
Selim Cineka1d97902016-12-14 16:31:40 -0800897 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700898 setOpenedAmount(openedAmount);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800899 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700900 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800901 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800902 }
Selim Cinek281c2022016-10-13 19:14:43 -0700903 }
904}