blob: 0a8b7f8aa0dae6824d2ed0542049cd075f78bcee [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
felkachangc02c3882018-07-27 15:32:31 +080019import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN_REVERSE;
Selim Cinek1f624952017-06-08 19:11:50 -070020import static com.android.systemui.statusbar.phone.NotificationIconContainer.IconState.NO_VALUE;
Selim Cinek1f624952017-06-08 19:11:50 -070021
Selim Cinek281c2022016-10-13 19:14:43 -070022import android.content.Context;
23import android.content.res.Configuration;
Anthony Chen9e05d462017-04-07 10:10:21 -070024import android.content.res.Resources;
Selim Cinek6eaacf22017-09-07 18:53:17 -070025import android.graphics.Rect;
Selim Cinek2b549f42016-11-22 16:38:51 -080026import android.os.SystemProperties;
Selim Cinek281c2022016-10-13 19:14:43 -070027import android.util.AttributeSet;
Selim Cinekb7bafbc2017-12-21 11:33:26 -080028import android.util.Log;
felkachangc02c3882018-07-27 15:32:31 +080029import android.view.DisplayCutout;
Selim Cinek281c2022016-10-13 19:14:43 -070030import android.view.View;
31import android.view.ViewGroup;
Selim Cinek6eaacf22017-09-07 18:53:17 -070032import android.view.ViewTreeObserver;
felkachangc02c3882018-07-27 15:32:31 +080033import android.view.WindowInsets;
Selim Cinekaca84c02017-04-05 16:28:56 -070034import android.view.accessibility.AccessibilityNodeInfo;
Selim Cinek1f624952017-06-08 19:11:50 -070035
Jason Monk297c04e2018-08-23 17:16:59 -040036import com.android.internal.annotations.VisibleForTesting;
Jason Monk1fd3fc32018-08-14 17:20:09 -040037import com.android.systemui.Dependency;
Selim Cinek9458b192016-10-25 19:02:42 -070038import com.android.systemui.Interpolators;
Selim Cinek281c2022016-10-13 19:14:43 -070039import com.android.systemui.R;
Beverly8fdb5332019-02-04 14:29:49 -050040import com.android.systemui.plugins.statusbar.StatusBarStateController;
41import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
Selim Cinek281c2022016-10-13 19:14:43 -070042import com.android.systemui.statusbar.notification.NotificationUtils;
Rohan Shah20790b82018-07-02 17:21:04 -070043import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
44import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
45import com.android.systemui.statusbar.notification.row.ExpandableView;
Rohan Shah20790b82018-07-02 17:21:04 -070046import com.android.systemui.statusbar.notification.stack.AmbientState;
47import com.android.systemui.statusbar.notification.stack.AnimationProperties;
48import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
49import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
Rohan Shah20790b82018-07-02 17:21:04 -070050import com.android.systemui.statusbar.notification.stack.ViewState;
Lucas Dupin23a8d3b2018-10-08 20:57:35 -070051import com.android.systemui.statusbar.phone.NotificationIconContainer;
Selim Cinek281c2022016-10-13 19:14:43 -070052
Selim Cinek281c2022016-10-13 19:14:43 -070053/**
54 * A notification shelf view that is placed inside the notification scroller. It manages the
55 * overflow icons that don't fit into the regular list anymore.
56 */
Selim Cinek9ef119c2017-03-01 15:13:36 -080057public class NotificationShelf extends ActivatableNotificationView implements
Lucas Dupin7fc9dc12019-01-03 09:19:43 -080058 View.OnLayoutChangeListener, StateListener {
Selim Cinek281c2022016-10-13 19:14:43 -070059
Selim Cinek2b549f42016-11-22 16:38:51 -080060 private static final boolean USE_ANIMATIONS_WHEN_OPENING =
61 SystemProperties.getBoolean("debug.icon_opening_animations", true);
Selim Cineka1d97902016-12-14 16:31:40 -080062 private static final boolean ICON_ANMATIONS_WHILE_SCROLLING
63 = SystemProperties.getBoolean("debug.icon_scroll_animations", true);
Selim Cinek6eaacf22017-09-07 18:53:17 -070064 private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
Selim Cinekb7bafbc2017-12-21 11:33:26 -080065 private static final String TAG = "NotificationShelf";
Lucas Dupinb561eda2018-04-09 17:25:04 -070066
Selim Cinek49014f82016-11-04 14:55:30 -070067 private NotificationIconContainer mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -070068 private int[] mTmp = new int[2];
69 private boolean mHideBackground;
70 private int mIconAppearTopPadding;
Selim Cinek195dfc52019-05-30 19:35:05 -070071 private float mHiddenShelfIconSize;
Selim Cinek48ff9b42016-11-09 19:31:51 -080072 private int mStatusBarHeight;
73 private int mStatusBarPaddingStart;
Selim Cinekc383fd02016-10-21 15:31:26 -070074 private AmbientState mAmbientState;
75 private NotificationStackScrollLayout mHostLayout;
Selim Cinek9458b192016-10-25 19:02:42 -070076 private int mMaxLayoutHeight;
Selim Cineka686b2c2016-10-26 13:58:27 -070077 private int mPaddingBetweenElements;
Selim Cinekeccb5de2016-10-28 15:04:05 -070078 private int mNotGoneIndex;
79 private boolean mHasItemsInStableShelf;
Selim Cinek49014f82016-11-04 14:55:30 -070080 private NotificationIconContainer mCollapsedIcons;
Selim Cinek727903c2016-12-06 17:28:10 -080081 private int mScrollFastThreshold;
Selim Cinekb42698f2017-07-31 17:47:45 -070082 private int mIconSize;
Selim Cinek810bcde2016-12-14 17:29:23 -080083 private int mStatusBarState;
Selim Cineka1d97902016-12-14 16:31:40 -080084 private float mMaxShelfEnd;
Selim Cinekfcff4c62016-12-27 14:26:06 +010085 private int mRelativeOffset;
Selim Cinekc6813462017-01-13 17:10:38 -080086 private boolean mInteractive;
Selim Cinek2fce3c82017-05-08 12:38:09 -070087 private float mOpenedAmount;
88 private boolean mNoAnimationsInThisFrame;
Selim Cinek09bd29d2017-02-03 15:30:28 -080089 private boolean mAnimationsEnabled = true;
Anthony Chen9e05d462017-04-07 10:10:21 -070090 private boolean mShowNotificationShelf;
Selim Cinek515b2032017-11-15 10:20:19 -080091 private float mFirstElementRoundness;
Selim Cinek143672c2018-03-23 20:04:32 -070092 private Rect mClipRect = new Rect();
felkachangc02c3882018-07-27 15:32:31 +080093 private int mCutoutHeight;
Gus Prevase2d6f042018-10-17 15:25:30 -040094 private int mGapHeight;
Selim Cinek281c2022016-10-13 19:14:43 -070095
96 public NotificationShelf(Context context, AttributeSet attrs) {
97 super(context, attrs);
98 }
99
100 @Override
Jason Monk297c04e2018-08-23 17:16:59 -0400101 @VisibleForTesting
102 public void onFinishInflate() {
Selim Cinek281c2022016-10-13 19:14:43 -0700103 super.onFinishInflate();
Alan Viverette51efddb2017-04-05 10:00:01 -0400104 mShelfIcons = findViewById(R.id.content);
Selim Cinek49014f82016-11-04 14:55:30 -0700105 mShelfIcons.setClipChildren(false);
106 mShelfIcons.setClipToPadding(false);
107
Selim Cinek281c2022016-10-13 19:14:43 -0700108 setClipToActualHeight(false);
109 setClipChildren(false);
110 setClipToPadding(false);
Evan Lairdc987fc72017-12-15 10:14:22 -0500111 mShelfIcons.setIsStaticLayout(false);
Selim Cinek2871bef2017-11-22 08:40:00 -0800112 setBottomRoundness(1.0f, false /* animate */);
Selim Cinek281c2022016-10-13 19:14:43 -0700113 initDimens();
114 }
115
Jason Monk1fd3fc32018-08-14 17:20:09 -0400116 @Override
117 protected void onAttachedToWindow() {
118 super.onAttachedToWindow();
Beverly8fdb5332019-02-04 14:29:49 -0500119 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
120 .addCallback(this, SysuiStatusBarStateController.RANK_SHELF);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400121 }
122
123 @Override
124 protected void onDetachedFromWindow() {
125 super.onDetachedFromWindow();
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800126 Dependency.get(StatusBarStateController.class).removeCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400127 }
128
Selim Cinekc383fd02016-10-21 15:31:26 -0700129 public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
130 mAmbientState = ambientState;
131 mHostLayout = hostLayout;
132 }
133
Selim Cinek281c2022016-10-13 19:14:43 -0700134 private void initDimens() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700135 Resources res = getResources();
136 mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
137 mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
138 mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
139 mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
140
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800141 ViewGroup.LayoutParams layoutParams = getLayoutParams();
Anthony Chen9e05d462017-04-07 10:10:21 -0700142 layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800143 setLayoutParams(layoutParams);
Anthony Chen9e05d462017-04-07 10:10:21 -0700144
145 int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800146 mShelfIcons.setPadding(padding, 0, padding, 0);
Anthony Chen9e05d462017-04-07 10:10:21 -0700147 mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
148 mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
Selim Cinekb42698f2017-07-31 17:47:45 -0700149 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
Selim Cinek195dfc52019-05-30 19:35:05 -0700150 mHiddenShelfIconSize = res.getDimensionPixelOffset(R.dimen.hidden_shelf_icon_size);
Gus Prevase2d6f042018-10-17 15:25:30 -0400151 mGapHeight = res.getDimensionPixelSize(R.dimen.qs_notification_padding);
Anthony Chen9e05d462017-04-07 10:10:21 -0700152
153 if (!mShowNotificationShelf) {
154 setVisibility(GONE);
155 }
Selim Cinek281c2022016-10-13 19:14:43 -0700156 }
157
158 @Override
159 protected void onConfigurationChanged(Configuration newConfig) {
160 super.onConfigurationChanged(newConfig);
161 initDimens();
162 }
163
164 @Override
Selim Cinek281c2022016-10-13 19:14:43 -0700165 protected View getContentView() {
Selim Cinek49014f82016-11-04 14:55:30 -0700166 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700167 }
168
Selim Cinek49014f82016-11-04 14:55:30 -0700169 public NotificationIconContainer getShelfIcons() {
170 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700171 }
172
173 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500174 public ExpandableViewState createExpandableViewState() {
175 return new ShelfState();
Selim Cinek281c2022016-10-13 19:14:43 -0700176 }
177
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500178 /** Update the state of the shelf. */
179 public void updateState(AmbientState ambientState) {
180 ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
181 ShelfState viewState = (ShelfState) getViewState();
Anthony Chen9e05d462017-04-07 10:10:21 -0700182 if (mShowNotificationShelf && lastView != null) {
Selim Cinek281c2022016-10-13 19:14:43 -0700183 float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
184 + ambientState.getStackTranslation();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500185 ExpandableViewState lastViewState = lastView.getViewState();
Selim Cinek281c2022016-10-13 19:14:43 -0700186 float viewEnd = lastViewState.yTranslation + lastViewState.height;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500187 viewState.copyFrom(lastViewState);
188 viewState.height = getIntrinsicHeight();
Lucas Dupinb561eda2018-04-09 17:25:04 -0700189
Selim Cinek195dfc52019-05-30 19:35:05 -0700190 viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
Selim Cinek49014f82016-11-04 14:55:30 -0700191 getFullyClosedTranslation());
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500192 viewState.zTranslation = ambientState.getBaseZHeight();
felkachangc02c3882018-07-27 15:32:31 +0800193 // For the small display size, it's not enough to make the icon not covered by
194 // the top cutout so the denominator add the height of cutout.
195 // Totally, (getIntrinsicHeight() * 2 + mCutoutHeight) should be smaller then
196 // mAmbientState.getTopPadding().
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500197 float openedAmount = (viewState.yTranslation - getFullyClosedTranslation())
felkachangc02c3882018-07-27 15:32:31 +0800198 / (getIntrinsicHeight() * 2 + mCutoutHeight);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800199 openedAmount = Math.min(1.0f, openedAmount);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500200 viewState.openedAmount = openedAmount;
201 viewState.clipTopAmount = 0;
Selim Cinek459aee32019-02-20 11:18:56 -0800202 viewState.alpha = 1;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500203 viewState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
204 viewState.hideSensitive = false;
205 viewState.xTranslation = getTranslationX();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700206 if (mNotGoneIndex != -1) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500207 viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700208 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500209 viewState.hasItemsInStableShelf = lastViewState.inShelf;
210 viewState.hidden = !mAmbientState.isShadeExpanded()
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800211 || mAmbientState.isQsCustomizerShowing();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500212 viewState.maxShelfEnd = maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700213 } else {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500214 viewState.hidden = true;
215 viewState.location = ExpandableViewState.LOCATION_GONE;
216 viewState.hasItemsInStableShelf = false;
Selim Cinek281c2022016-10-13 19:14:43 -0700217 }
218 }
219
Selim Cinekc383fd02016-10-21 15:31:26 -0700220 /**
221 * Update the shelf appearance based on the other notifications around it. This transforms
222 * the icons from the notification area into the shelf.
223 */
224 public void updateAppearance() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700225 // If the shelf should not be shown, then there is no need to update anything.
226 if (!mShowNotificationShelf) {
227 return;
228 }
229
Selim Cinek65d418e2016-11-29 15:42:34 -0800230 mShelfIcons.resetViewStates();
231 float shelfStart = getTranslationY();
Selim Cinekdb167372016-11-17 15:41:17 -0800232 float numViewsInShelf = 0.0f;
233 View lastChild = mAmbientState.getLastVisibleBackgroundChild();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700234 mNotGoneIndex = -1;
Selim Cinek49014f82016-11-04 14:55:30 -0700235 float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
236 float expandAmount = 0.0f;
Selim Cinek65d418e2016-11-29 15:42:34 -0800237 if (shelfStart >= interpolationStart) {
238 expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
Selim Cinek49014f82016-11-04 14:55:30 -0700239 expandAmount = Math.min(1.0f, expandAmount);
240 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700241 // find the first view that doesn't overlap with the shelf
Selim Cinekdb167372016-11-17 15:41:17 -0800242 int notGoneIndex = 0;
Selim Cinekec29d342017-05-05 18:31:49 -0700243 int colorOfViewBeforeLast = NO_COLOR;
Selim Cinekdb167372016-11-17 15:41:17 -0800244 boolean backgroundForceHidden = false;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500245 if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
Selim Cinekdb167372016-11-17 15:41:17 -0800246 backgroundForceHidden = true;
247 }
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800248 int colorTwoBefore = NO_COLOR;
249 int previousColor = NO_COLOR;
250 float transitionAmount = 0.0f;
Selim Cineka1d97902016-12-14 16:31:40 -0800251 float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
252 boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
Selim Cinekd5ab6452016-12-08 16:34:00 -0800253 || (mAmbientState.isExpansionChanging()
254 && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
Selim Cineka1d97902016-12-14 16:31:40 -0800255 boolean scrolling = currentScrollVelocity > 0;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800256 boolean expandingAnimated = mAmbientState.isExpansionChanging()
257 && !mAmbientState.isPanelTracking();
Selim Cinek65d418e2016-11-29 15:42:34 -0800258 int baseZHeight = mAmbientState.getBaseZHeight();
Selim Cinek515b2032017-11-15 10:20:19 -0800259 int backgroundTop = 0;
Selim Cinek5040f2e2019-02-14 18:22:42 -0800260 int clipTopAmount = 0;
Selim Cinek515b2032017-11-15 10:20:19 -0800261 float firstElementRoundness = 0.0f;
Aran Inkc8d943e2019-05-30 15:44:12 -0400262 ActivatableNotificationView previousRow = null;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700263
264 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
265 ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
266
Aran Inkc8d943e2019-05-30 15:44:12 -0400267 if (!(child instanceof ActivatableNotificationView)
268 || child.getVisibility() == GONE || child == this) {
Selim Cinekc383fd02016-10-21 15:31:26 -0700269 continue;
270 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700271
Aran Inkc8d943e2019-05-30 15:44:12 -0400272 ActivatableNotificationView row = (ActivatableNotificationView) child;
Selim Cineka686b2c2016-10-26 13:58:27 -0700273 float notificationClipEnd;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800274 boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight
275 || row.isPinned();
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800276 boolean isLastChild = child == lastChild;
Selim Cinek65d418e2016-11-29 15:42:34 -0800277 float rowTranslationY = row.getTranslationY();
Selim Cinek3ff9fba2017-07-20 10:47:48 -0700278 if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700279 notificationClipEnd = shelfStart + getIntrinsicHeight();
280 } else {
Selim Cineka686b2c2016-10-26 13:58:27 -0700281 notificationClipEnd = shelfStart - mPaddingBetweenElements;
Selim Cinek65d418e2016-11-29 15:42:34 -0800282 float height = notificationClipEnd - rowTranslationY;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800283 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700284 // We want the gap to close when we reached the minimum size and only shrink
285 // before
286 notificationClipEnd = Math.min(shelfStart,
Selim Cinek65d418e2016-11-29 15:42:34 -0800287 rowTranslationY + getNotificationMergeSize());
Selim Cineka686b2c2016-10-26 13:58:27 -0700288 }
289 }
Selim Cinek5040f2e2019-02-14 18:22:42 -0800290 int clipTop = updateNotificationClipHeight(row, notificationClipEnd, notGoneIndex);
291 clipTopAmount = Math.max(clipTop, clipTopAmount);
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800292
Aran Inkc8d943e2019-05-30 15:44:12 -0400293 // If the current row is an ExpandableNotificationRow, update its color, roundedness,
294 // and icon state.
295 if (row instanceof ExpandableNotificationRow) {
296 ExpandableNotificationRow expandableRow = (ExpandableNotificationRow) row;
297
298 float inShelfAmount = updateIconAppearance(expandableRow, expandAmount, scrolling,
299 scrollingFast,
300 expandingAnimated, isLastChild);
301 numViewsInShelf += inShelfAmount;
302 int ownColorUntinted = row.getBackgroundColorWithoutTint();
303 if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
304 mNotGoneIndex = notGoneIndex;
305 setTintColor(previousColor);
306 setOverrideTintColor(colorTwoBefore, transitionAmount);
307
308 } else if (mNotGoneIndex == -1) {
309 colorTwoBefore = previousColor;
310 transitionAmount = inShelfAmount;
311 }
312 if (isLastChild) {
313 if (colorOfViewBeforeLast == NO_COLOR) {
314 colorOfViewBeforeLast = ownColorUntinted;
315 }
316 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
317 } else {
Selim Cinekec29d342017-05-05 18:31:49 -0700318 colorOfViewBeforeLast = ownColorUntinted;
Aran Inkc8d943e2019-05-30 15:44:12 -0400319 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
Selim Cinekec29d342017-05-05 18:31:49 -0700320 }
Aran Inkc8d943e2019-05-30 15:44:12 -0400321 if (notGoneIndex != 0 || !aboveShelf) {
322 expandableRow.setAboveShelf(false);
Selim Cinek0fe07392017-11-09 13:26:34 -0800323 }
Aran Inkc8d943e2019-05-30 15:44:12 -0400324 if (notGoneIndex == 0) {
325 StatusBarIconView icon = expandableRow.getEntry().expandedIcon;
326 NotificationIconContainer.IconState iconState = getIconState(icon);
327 // The icon state might be null in rare cases where the notification is actually
328 // added to the layout, but not to the shelf. An example are replied messages,
329 // since they don't show up on AOD
330 if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
331 // only if the first icon is fully in the shelf we want to clip to it!
332 backgroundTop = (int) (row.getTranslationY() - getTranslationY());
333 firstElementRoundness = row.getCurrentTopRoundness();
334 }
335 }
336
337 previousColor = ownColorUntinted;
338 notGoneIndex++;
Selim Cinek0fe07392017-11-09 13:26:34 -0800339 }
Aran Inkc8d943e2019-05-30 15:44:12 -0400340
Gus Prevase2d6f042018-10-17 15:25:30 -0400341 if (row.isFirstInSection() && previousRow != null && previousRow.isLastInSection()) {
342 // If the top of the shelf is between the view before a gap and the view after a gap
343 // then we need to adjust the shelf's top roundness.
344 float distanceToGapBottom = row.getTranslationY() - getTranslationY();
345 float distanceToGapTop = getTranslationY()
346 - (previousRow.getTranslationY() + previousRow.getActualHeight());
347 if (distanceToGapTop > 0) {
348 // We interpolate our top roundness so that it's fully rounded if we're at the
349 // bottom of the gap, and not rounded at all if we're at the top of the gap
350 // (directly up against the bottom of previousRow)
351 // Then we apply the same roundness to the bottom of previousRow so that the
352 // corners join together as the shelf approaches previousRow.
353 firstElementRoundness = (float) Math.min(1.0, distanceToGapTop / mGapHeight);
354 previousRow.setBottomRoundness(firstElementRoundness,
355 false /* don't animate */);
356 backgroundTop = (int) distanceToGapBottom;
357 }
358 }
Gus Prevase2d6f042018-10-17 15:25:30 -0400359 previousRow = row;
Selim Cineka686b2c2016-10-26 13:58:27 -0700360 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700361 clipTransientViews();
362
Selim Cinek5040f2e2019-02-14 18:22:42 -0800363 setClipTopAmount(clipTopAmount);
Selim Cinek515b2032017-11-15 10:20:19 -0800364 setBackgroundTop(backgroundTop);
365 setFirstElementRoundness(firstElementRoundness);
Selim Cinek17e1b692016-12-02 18:19:11 -0800366 mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
Selim Cinek49014f82016-11-04 14:55:30 -0700367 mShelfIcons.calculateIconTranslations();
368 mShelfIcons.applyIconStates();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700369 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
370 View child = mHostLayout.getChildAt(i);
371 if (!(child instanceof ExpandableNotificationRow)
372 || child.getVisibility() == GONE) {
373 continue;
374 }
375 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
376 updateIconClipAmount(row);
377 updateContinuousClipping(row);
378 }
Selim Cinekdb167372016-11-17 15:41:17 -0800379 boolean hideBackground = numViewsInShelf < 1.0f;
380 setHideBackground(hideBackground || backgroundForceHidden);
381 if (mNotGoneIndex == -1) {
382 mNotGoneIndex = notGoneIndex;
383 }
Selim Cinek48ff9b42016-11-09 19:31:51 -0800384 }
385
Rohan Shah524cf7b2018-03-15 14:40:02 -0700386 /**
387 * Clips transient views to the top of the shelf - Transient views are only used for
388 * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
389 * don't show underneath the notification stack when something is animating and the user
390 * swipes quickly.
391 */
392 private void clipTransientViews() {
393 for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
394 View transientView = mHostLayout.getTransientView(i);
395 if (transientView instanceof ExpandableNotificationRow) {
396 ExpandableNotificationRow transientRow = (ExpandableNotificationRow) transientView;
Selim Cinek5040f2e2019-02-14 18:22:42 -0800397 updateNotificationClipHeight(transientRow, getTranslationY(), -1);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700398 } else {
399 Log.e(TAG, "NotificationShelf.clipTransientViews(): "
400 + "Trying to clip non-row transient view");
401 }
402 }
403 }
404
Selim Cinek515b2032017-11-15 10:20:19 -0800405 private void setFirstElementRoundness(float firstElementRoundness) {
406 if (mFirstElementRoundness != firstElementRoundness) {
407 mFirstElementRoundness = firstElementRoundness;
408 setTopRoundness(firstElementRoundness, false /* animate */);
Selim Cinek0fe07392017-11-09 13:26:34 -0800409 }
410 }
411
Selim Cinek6eaacf22017-09-07 18:53:17 -0700412 private void updateIconClipAmount(ExpandableNotificationRow row) {
413 float maxTop = row.getTranslationY();
Selim Cinek5040f2e2019-02-14 18:22:42 -0800414 if (getClipTopAmount() != 0) {
415 // if the shelf is clipped, lets make sure we also clip the icon
416 maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
417 }
Selim Cinek6eaacf22017-09-07 18:53:17 -0700418 StatusBarIconView icon = row.getEntry().expandedIcon;
419 float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
Selim Cinek195dfc52019-05-30 19:35:05 -0700420 if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) {
Selim Cinek6eaacf22017-09-07 18:53:17 -0700421 int top = (int) (maxTop - shelfIconPosition);
422 Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight()));
423 icon.setClipBounds(clipRect);
Gus Prevasab3ad1d2018-11-01 17:11:59 -0400424 } else {
425 icon.setClipBounds(null);
Selim Cinek6eaacf22017-09-07 18:53:17 -0700426 }
427 }
428
429 private void updateContinuousClipping(final ExpandableNotificationRow row) {
430 StatusBarIconView icon = row.getEntry().expandedIcon;
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700431 boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700432 boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
433 if (needsContinuousClipping && !isContinuousClipping) {
Selim Cinek85845b82018-04-25 13:10:57 +0800434 final ViewTreeObserver observer = icon.getViewTreeObserver();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700435 ViewTreeObserver.OnPreDrawListener predrawListener =
436 new ViewTreeObserver.OnPreDrawListener() {
437 @Override
438 public boolean onPreDraw() {
439 boolean animatingY = ViewState.isAnimatingY(icon);
Selim Cinek85845b82018-04-25 13:10:57 +0800440 if (!animatingY) {
Robert Snoebergered77b452018-10-12 17:22:06 +0000441 if (observer.isAlive()) {
442 observer.removeOnPreDrawListener(this);
443 }
Selim Cinek6eaacf22017-09-07 18:53:17 -0700444 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
445 return true;
446 }
447 updateIconClipAmount(row);
448 return true;
449 }
450 };
Selim Cinek85845b82018-04-25 13:10:57 +0800451 observer.addOnPreDrawListener(predrawListener);
452 icon.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
453 @Override
454 public void onViewAttachedToWindow(View v) {
455 }
456
457 @Override
458 public void onViewDetachedFromWindow(View v) {
459 if (v == icon) {
Robert Snoebergered77b452018-10-12 17:22:06 +0000460 if (observer.isAlive()) {
461 observer.removeOnPreDrawListener(predrawListener);
462 }
Selim Cinek85845b82018-04-25 13:10:57 +0800463 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
464 }
465 }
466 });
Selim Cinek6eaacf22017-09-07 18:53:17 -0700467 icon.setTag(TAG_CONTINUOUS_CLIPPING, predrawListener);
468 }
469 }
470
Selim Cinek4fc661e2019-02-19 17:42:36 -0800471 /**
472 * Update the clipping of this view.
473 * @return the amount that our own top should be clipped
474 */
Aran Inkc8d943e2019-05-30 15:44:12 -0400475 private int updateNotificationClipHeight(ActivatableNotificationView row,
Selim Cinek5040f2e2019-02-14 18:22:42 -0800476 float notificationClipEnd, int childIndex) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700477 float viewEnd = row.getTranslationY() + row.getActualHeight();
Selim Cinekebf42342017-07-13 15:46:10 +0200478 boolean isPinned = (row.isPinned() || row.isHeadsUpAnimatingAway())
479 && !mAmbientState.isDozingAndNotPulsing(row);
Selim Cinekc3fec682019-06-06 18:11:07 -0700480 boolean shouldClipOwnTop;
481 if (mAmbientState.isPulseExpanding()) {
482 shouldClipOwnTop = childIndex == 0;
483 } else {
484 shouldClipOwnTop = row.showingPulsing();
485 }
Selim Cinek4fc661e2019-02-19 17:42:36 -0800486 if (viewEnd > notificationClipEnd && !shouldClipOwnTop
Selim Cinek7e0f9482017-05-22 20:00:56 -0700487 && (mAmbientState.isShadeExpanded() || !isPinned)) {
488 int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
489 if (isPinned) {
490 clipBottomAmount = Math.min(row.getIntrinsicHeight() - row.getCollapsedHeight(),
491 clipBottomAmount);
492 }
Selim Cinek4fc661e2019-02-19 17:42:36 -0800493 row.setClipBottomAmount(clipBottomAmount);
Selim Cineka686b2c2016-10-26 13:58:27 -0700494 } else {
495 row.setClipBottomAmount(0);
496 }
Selim Cinek4fc661e2019-02-19 17:42:36 -0800497 if (shouldClipOwnTop) {
498 return (int) (viewEnd - getTranslationY());
499 } else {
500 return 0;
501 }
Selim Cineka686b2c2016-10-26 13:58:27 -0700502 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700503
Selim Cinekc8c4cf92017-09-08 15:30:09 -0700504 @Override
505 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
506 int outlineTranslation) {
507 if (!mHasItemsInStableShelf) {
508 shadowIntensity = 0.0f;
509 }
510 super.setFakeShadowIntensity(shadowIntensity, outlineAlpha, shadowYEnd, outlineTranslation);
511 }
512
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800513 /**
514 * @return the icon amount how much this notification is in the shelf;
515 */
Selim Cinek2b549f42016-11-22 16:38:51 -0800516 private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800517 boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
518 boolean isLastChild) {
Selim Cinek1f624952017-06-08 19:11:50 -0700519 StatusBarIconView icon = row.getEntry().expandedIcon;
520 NotificationIconContainer.IconState iconState = getIconState(icon);
521 if (iconState == null) {
522 return 0.0f;
523 }
524
Selim Cinekc383fd02016-10-21 15:31:26 -0700525 // Let calculate how much the view is in the shelf
526 float viewStart = row.getTranslationY();
Selim Cinek2b549f42016-11-22 16:38:51 -0800527 int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
528 float iconTransformDistance = getIntrinsicHeight() * 1.5f;
Selim Cineka1d97902016-12-14 16:31:40 -0800529 iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
Selim Cinek1f624952017-06-08 19:11:50 -0700530 iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
Selim Cinek938bdaa2016-11-18 16:31:09 -0800531 if (isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800532 fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
Bill Line6065e32018-06-08 17:07:13 +0800533 iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
534 - getIntrinsicHeight());
Selim Cinek938bdaa2016-11-18 16:31:09 -0800535 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800536 float viewEnd = viewStart + fullHeight;
Gus Prevas0fa58d62019-01-11 13:58:40 -0500537 // TODO: fix this check for anchor scrolling.
Selim Cinek1f624952017-06-08 19:11:50 -0700538 if (expandingAnimated && mAmbientState.getScrollY() == 0
539 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
540 // We are expanding animated. Because we switch to a linear interpolation in this case,
541 // the last icon may be stuck in between the shelf position and the notification
542 // position, which looks pretty bad. We therefore optimize this case by applying a
543 // shorter transition such that the icon is either fully in the notification or we clamp
544 // it into the shelf if it's close enough.
545 // We need to persist this, since after the expansion, the behavior should still be the
546 // same.
547 float position = mAmbientState.getIntrinsicPadding()
548 + mHostLayout.getPositionInLinearLayout(row);
549 int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
550 if (position < maxShelfStart && position + row.getIntrinsicHeight() >= maxShelfStart
551 && row.getTranslationY() < position) {
552 iconState.isLastExpandIcon = true;
553 iconState.customTransformHeight = NO_VALUE;
554 // Let's check if we're close enough to snap into the shelf
555 boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
556 < getIntrinsicHeight();
557 if (!forceInShelf) {
558 // We are overlapping the shelf but not enough, so the icon needs to be
559 // repositioned
560 iconState.customTransformHeight = (int) (mMaxLayoutHeight
561 - getIntrinsicHeight() - position);
562 }
563 }
564 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800565 float fullTransitionAmount;
Selim Cinek01a73f92016-12-06 16:13:42 -0800566 float iconTransitionAmount;
567 float shelfStart = getTranslationY();
Selim Cinek1f624952017-06-08 19:11:50 -0700568 if (iconState.hasCustomTransformHeight()) {
569 fullHeight = iconState.customTransformHeight;
570 iconTransformDistance = iconState.customTransformHeight;
571 }
572 boolean fullyInOrOut = true;
Selim Cinekec29d342017-05-05 18:31:49 -0700573 if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
574 && (mAmbientState.isShadeExpanded()
575 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800576 if (viewStart < shelfStart) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800577 float fullAmount = (shelfStart - viewStart) / fullHeight;
Selim Cinek1f624952017-06-08 19:11:50 -0700578 fullAmount = Math.min(1.0f, fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700579 float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
Selim Cinek2b549f42016-11-22 16:38:51 -0800580 fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700581 interpolatedAmount = NotificationUtils.interpolate(
Selim Cinek2b549f42016-11-22 16:38:51 -0800582 interpolatedAmount, fullAmount, expandAmount);
583 fullTransitionAmount = 1.0f - interpolatedAmount;
584
Selim Cinek01a73f92016-12-06 16:13:42 -0800585 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
586 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
587 iconTransitionAmount = 1.0f - iconTransitionAmount;
Selim Cinek1f624952017-06-08 19:11:50 -0700588 fullyInOrOut = false;
Selim Cinekc383fd02016-10-21 15:31:26 -0700589 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800590 fullTransitionAmount = 1.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800591 iconTransitionAmount = 1.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700592 }
593 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800594 fullTransitionAmount = 0.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800595 iconTransitionAmount = 0.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700596 }
Selim Cinek1f624952017-06-08 19:11:50 -0700597 if (fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
598 iconState.isLastExpandIcon = false;
599 iconState.customTransformHeight = NO_VALUE;
600 }
Selim Cineka1d97902016-12-14 16:31:40 -0800601 updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
602 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800603 return fullTransitionAmount;
604 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700605
Selim Cinek2b549f42016-11-22 16:38:51 -0800606 private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800607 float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
608 boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800609 StatusBarIconView icon = row.getEntry().expandedIcon;
610 NotificationIconContainer.IconState iconState = getIconState(icon);
611 if (iconState == null) {
612 return;
613 }
Selim Cinek1f624952017-06-08 19:11:50 -0700614 boolean forceInShelf = iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
Selim Cinek2b549f42016-11-22 16:38:51 -0800615 float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800616 if (clampedAmount == fullTransitionAmount) {
Selim Cinek1f624952017-06-08 19:11:50 -0700617 iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
Selim Cinek44d81a62017-05-08 19:45:40 -0700618 iconState.useFullTransitionAmount = iconState.noAnimations
Selim Cineka1d97902016-12-14 16:31:40 -0800619 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
620 iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
621 && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
Selim Cinek01a73f92016-12-06 16:13:42 -0800622 iconState.translateContent = mMaxLayoutHeight - getTranslationY()
623 - getIntrinsicHeight() > 0;
Selim Cinek2b549f42016-11-22 16:38:51 -0800624 }
Selim Cinek1f624952017-06-08 19:11:50 -0700625 if (!forceInShelf && (scrollingFast || (expandingAnimated
626 && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) {
Selim Cinekd5ab6452016-12-08 16:34:00 -0800627 iconState.cancelAnimations(icon);
628 iconState.useFullTransitionAmount = true;
Selim Cinek44d81a62017-05-08 19:45:40 -0700629 iconState.noAnimations = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800630 }
Selim Cinek1f624952017-06-08 19:11:50 -0700631 if (iconState.hasCustomTransformHeight()) {
632 iconState.useFullTransitionAmount = true;
633 }
634 if (iconState.isLastExpandIcon) {
635 iconState.translateContent = false;
636 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800637 float transitionAmount;
Selim Cinek195dfc52019-05-30 19:35:05 -0700638 if (mAmbientState.isHiddenAtAll() && !row.isInShelf()) {
639 transitionAmount = mAmbientState.isFullyHidden() ? 1 : 0;
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800640 } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
Selim Cineka1d97902016-12-14 16:31:40 -0800641 || iconState.useLinearTransitionAmount) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800642 transitionAmount = iconTransitionAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800643 } else {
Selim Cineka1d97902016-12-14 16:31:40 -0800644 // We take the clamped position instead
645 transitionAmount = clampedAmount;
Selim Cinek2fce3c82017-05-08 12:38:09 -0700646 iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount
647 && !mNoAnimationsInThisFrame;
Selim Cinek2b549f42016-11-22 16:38:51 -0800648 }
649 iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
650 || iconState.useFullTransitionAmount
651 ? fullTransitionAmount
652 : transitionAmount;
653 iconState.clampedAppearAmount = clampedAmount;
Selim Cinekc3fec682019-06-06 18:11:07 -0700654 float contentTransformationAmount = !row.isAboveShelf() && !row.showingPulsing()
Selim Cinek7e0f9482017-05-22 20:00:56 -0700655 && (isLastChild || iconState.translateContent)
Selim Cinek01a73f92016-12-06 16:13:42 -0800656 ? iconTransitionAmount
657 : 0.0f;
658 row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
Selim Cineka1d97902016-12-14 16:31:40 -0800659 setIconTransformationAmount(row, transitionAmount, iconTransformDistance,
Mady Mellor434180c2017-02-13 11:29:42 -0800660 clampedAmount != transitionAmount, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800661 }
662
663 private void setIconTransformationAmount(ExpandableNotificationRow row,
Mady Mellor434180c2017-02-13 11:29:42 -0800664 float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
665 boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800666 StatusBarIconView icon = row.getEntry().expandedIcon;
667 NotificationIconContainer.IconState iconState = getIconState(icon);
668
Selim Cinekc383fd02016-10-21 15:31:26 -0700669 View rowIcon = row.getNotificationIcon();
Selim Cineka1d97902016-12-14 16:31:40 -0800670 float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
Selim Cinekf20254c2017-02-03 10:09:33 -0800671 boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
672 if (usingLinearInterpolation && !stayingInShelf) {
Selim Cineka1d97902016-12-14 16:31:40 -0800673 // If we interpolate from the notification position, this might lead to a slightly
674 // odd interpolation, since the notification position changes as well. Let's interpolate
675 // from a fixed distance. We can only do this if we don't animate and the icon is
676 // always in the interpolated positon.
Selim Cinekf20254c2017-02-03 10:09:33 -0800677 notificationIconPosition = getTranslationY() - iconTransformDistance;
Selim Cineka1d97902016-12-14 16:31:40 -0800678 }
Selim Cinek281c2022016-10-13 19:14:43 -0700679 float notificationIconSize = 0.0f;
680 int iconTopPadding;
681 if (rowIcon != null) {
Selim Cinek875a3a12016-11-18 17:52:16 -0800682 iconTopPadding = row.getRelativeTopPadding(rowIcon);
Selim Cinek281c2022016-10-13 19:14:43 -0700683 notificationIconSize = rowIcon.getHeight();
684 } else {
685 iconTopPadding = mIconAppearTopPadding;
686 }
687 notificationIconPosition += iconTopPadding;
Selim Cinekc383fd02016-10-21 15:31:26 -0700688 float shelfIconPosition = getTranslationY() + icon.getTop();
Selim Cinekc3fec682019-06-06 18:11:07 -0700689 float iconSize = mAmbientState.isFullyHidden() ? mHiddenShelfIconSize : mIconSize;
Lucas Dupinc104cd62019-05-13 14:22:30 -0700690 shelfIconPosition += (icon.getHeight() - icon.getIconScale() * iconSize) / 2.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800691 float iconYTranslation = NotificationUtils.interpolate(
Selim Cineka1d97902016-12-14 16:31:40 -0800692 notificationIconPosition - shelfIconPosition,
Selim Cinek2b549f42016-11-22 16:38:51 -0800693 0,
694 transitionAmount);
Lucas Dupinc104cd62019-05-13 14:22:30 -0700695 float shelfIconSize = iconSize * icon.getIconScale();
Selim Cinek2b549f42016-11-22 16:38:51 -0800696 float alpha = 1.0f;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800697 boolean noIcon = !row.isShowingIcon();
698 if (noIcon) {
Selim Cinekc383fd02016-10-21 15:31:26 -0700699 // The view currently doesn't have an icon, lets transform it in!
Selim Cinekdb167372016-11-17 15:41:17 -0800700 alpha = transitionAmount;
Selim Cinekc383fd02016-10-21 15:31:26 -0700701 notificationIconSize = shelfIconSize / 2.0f;
702 }
703 // The notification size is different from the size in the shelf / statusbar
704 float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
Selim Cinek281c2022016-10-13 19:14:43 -0700705 transitionAmount);
Selim Cinekdb167372016-11-17 15:41:17 -0800706 if (iconState != null) {
Selim Cinekb42698f2017-07-31 17:47:45 -0700707 iconState.scaleX = newSize / shelfIconSize;
Selim Cinekdb167372016-11-17 15:41:17 -0800708 iconState.scaleY = iconState.scaleX;
Adrian Roos28f90c72017-05-08 17:24:26 -0700709 iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
Selim Cinekf38d6c32017-06-28 15:44:02 +0200710 boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
711 if (isAppearing) {
712 iconState.hidden = true;
713 iconState.iconAppearAmount = 0.0f;
714 }
Selim Cinekdb167372016-11-17 15:41:17 -0800715 iconState.alpha = alpha;
Selim Cinek2b549f42016-11-22 16:38:51 -0800716 iconState.yTranslation = iconYTranslation;
Selim Cinekf20254c2017-02-03 10:09:33 -0800717 if (stayingInShelf) {
Selim Cinekdb167372016-11-17 15:41:17 -0800718 iconState.iconAppearAmount = 1.0f;
719 iconState.alpha = 1.0f;
720 iconState.scaleX = 1.0f;
721 iconState.scaleY = 1.0f;
722 iconState.hidden = false;
723 }
Selim Cinekc3fec682019-06-06 18:11:07 -0700724 if (row.isAboveShelf()
725 || row.showingPulsing()
726 || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
Selim Cinek47374632017-03-17 16:07:17 -0700727 || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
Selim Cinek5ea19572016-11-29 15:34:48 -0800728 iconState.hidden = true;
729 }
Lucas Dupin83519da2017-06-21 11:58:31 -0700730 int backgroundColor = getBackgroundColorWithoutTint();
731 int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800732 if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
Lucas Dupinb6ed63b2017-05-30 16:17:42 -0700733 int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
734 shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
Selim Cinek875ba9b2017-02-13 16:20:17 -0800735 iconState.iconAppearAmount);
736 }
737 iconState.iconColor = shelfColor;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700738 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800739 }
740
741 private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
742 return mShelfIcons.getIconState(icon);
Selim Cinekc383fd02016-10-21 15:31:26 -0700743 }
744
745 private float getFullyClosedTranslation() {
746 return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek281c2022016-10-13 19:14:43 -0700747 }
748
Selim Cinek281c2022016-10-13 19:14:43 -0700749 public int getNotificationMergeSize() {
750 return getIntrinsicHeight();
751 }
752
753 @Override
754 public boolean hasNoContentHeight() {
755 return true;
756 }
Selim Cineka686b2c2016-10-26 13:58:27 -0700757
Selim Cinek281c2022016-10-13 19:14:43 -0700758 private void setHideBackground(boolean hideBackground) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800759 if (mHideBackground != hideBackground) {
760 mHideBackground = hideBackground;
761 updateBackground();
762 updateOutline();
763 }
Selim Cinekad7fac02016-10-18 17:09:15 -0700764 }
765
766 @Override
767 protected boolean needsOutline() {
Selim Cinek195dfc52019-05-30 19:35:05 -0700768 return !mHideBackground && super.needsOutline();
Selim Cinek281c2022016-10-13 19:14:43 -0700769 }
770
771 @Override
772 protected boolean shouldHideBackground() {
Selim Cinek195dfc52019-05-30 19:35:05 -0700773 return super.shouldHideBackground() || mHideBackground;
Selim Cinek281c2022016-10-13 19:14:43 -0700774 }
775
Selim Cinekfcff4c62016-12-27 14:26:06 +0100776 @Override
777 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
778 super.onLayout(changed, left, top, right, bottom);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800779 updateRelativeOffset();
Selim Cinek143672c2018-03-23 20:04:32 -0700780
781 // we always want to clip to our sides, such that nothing can draw outside of these bounds
782 int height = getResources().getDisplayMetrics().heightPixels;
783 mClipRect.set(0, -height, getWidth(), height);
784 mShelfIcons.setClipBounds(mClipRect);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800785 }
786
787 private void updateRelativeOffset() {
Selim Cinek49014f82016-11-04 14:55:30 -0700788 mCollapsedIcons.getLocationOnScreen(mTmp);
Selim Cinekfcff4c62016-12-27 14:26:06 +0100789 mRelativeOffset = mTmp[0];
790 getLocationOnScreen(mTmp);
791 mRelativeOffset -= mTmp[0];
792 }
793
felkachangc02c3882018-07-27 15:32:31 +0800794 @Override
795 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
796 WindowInsets ret = super.onApplyWindowInsets(insets);
797
798 // NotificationShelf drag from the status bar and the status bar dock on the top
799 // of the display for current design so just focus on the top of ScreenDecorations.
800 // In landscape or multiple window split mode, the NotificationShelf still drag from
801 // the top and the physical notch/cutout goes to the right, left, or both side of the
802 // display so it doesn't matter for the NotificationSelf in landscape.
803 DisplayCutout displayCutout = insets.getDisplayCutout();
804 mCutoutHeight = displayCutout == null || displayCutout.getSafeInsetTop() < 0
805 ? 0 : displayCutout.getSafeInsetTop();
806
807 return ret;
808 }
809
Selim Cinekfcff4c62016-12-27 14:26:06 +0100810 private void setOpenedAmount(float openedAmount) {
Selim Cinek2fce3c82017-05-08 12:38:09 -0700811 mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f;
812 mOpenedAmount = openedAmount;
Selim Cinekc1d9ab22019-05-21 18:08:30 -0700813 if (!mAmbientState.isPanelFullWidth() || mAmbientState.isDozing()) {
Selim Cinekfcff4c62016-12-27 14:26:06 +0100814 // We don't do a transformation at all, lets just assume we are fully opened
815 openedAmount = 1.0f;
816 }
817 int start = mRelativeOffset;
Selim Cinek49014f82016-11-04 14:55:30 -0700818 if (isLayoutRtl()) {
819 start = getWidth() - start - mCollapsedIcons.getWidth();
820 }
Evan Lairdc987fc72017-12-15 10:14:22 -0500821 int width = (int) NotificationUtils.interpolate(
822 start + mCollapsedIcons.getFinalTranslationX(),
Selim Cinek49014f82016-11-04 14:55:30 -0700823 mShelfIcons.getWidth(),
felkachangc02c3882018-07-27 15:32:31 +0800824 FAST_OUT_SLOW_IN_REVERSE.getInterpolation(openedAmount));
Selim Cinek49014f82016-11-04 14:55:30 -0700825 mShelfIcons.setActualLayoutWidth(width);
Selim Cinek932005d2016-12-05 17:12:09 -0800826 boolean hasOverflow = mCollapsedIcons.hasOverflow();
827 int collapsedPadding = mCollapsedIcons.getPaddingEnd();
828 if (!hasOverflow) {
829 // we have to ensure that adding the low priority notification won't lead to an
830 // overflow
Evan Laird8cf0de42018-02-06 18:34:55 -0500831 collapsedPadding -= mCollapsedIcons.getNoOverflowExtraPadding();
Evan Lairdc987fc72017-12-15 10:14:22 -0500832 } else {
833 // Partial overflow padding will fill enough space to add extra dots
834 collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
Selim Cinek932005d2016-12-05 17:12:09 -0800835 }
836 float padding = NotificationUtils.interpolate(collapsedPadding,
Selim Cinek49014f82016-11-04 14:55:30 -0700837 mShelfIcons.getPaddingEnd(),
838 openedAmount);
839 mShelfIcons.setActualPaddingEnd(padding);
840 float paddingStart = NotificationUtils.interpolate(start,
841 mShelfIcons.getPaddingStart(), openedAmount);
842 mShelfIcons.setActualPaddingStart(paddingStart);
Selim Cinek17e1b692016-12-02 18:19:11 -0800843 mShelfIcons.setOpenedAmount(openedAmount);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800844 }
845
Selim Cinek9458b192016-10-25 19:02:42 -0700846 public void setMaxLayoutHeight(int maxLayoutHeight) {
847 mMaxLayoutHeight = maxLayoutHeight;
848 }
849
Selim Cinekeccb5de2016-10-28 15:04:05 -0700850 /**
851 * @return the index of the notification at which the shelf visually resides
852 */
853 public int getNotGoneIndex() {
854 return mNotGoneIndex;
855 }
856
857 private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
Selim Cinek810bcde2016-12-14 17:29:23 -0800858 if (mHasItemsInStableShelf != hasItemsInStableShelf) {
859 mHasItemsInStableShelf = hasItemsInStableShelf;
860 updateInteractiveness();
861 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700862 }
863
864 /**
865 * @return whether the shelf has any icons in it when a potential animation has finished, i.e
866 * if the current state would be applied right now
867 */
868 public boolean hasItemsInStableShelf() {
869 return mHasItemsInStableShelf;
870 }
871
Selim Cinek49014f82016-11-04 14:55:30 -0700872 public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
873 mCollapsedIcons = collapsedIcons;
Selim Cinek9ef119c2017-03-01 15:13:36 -0800874 mCollapsedIcons.addOnLayoutChangeListener(this);
Selim Cinek49014f82016-11-04 14:55:30 -0700875 }
876
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800877 @Override
878 public void onStateChanged(int newState) {
879 mStatusBarState = newState;
Jason Monk1fd3fc32018-08-14 17:20:09 -0400880 updateInteractiveness();
Selim Cinek810bcde2016-12-14 17:29:23 -0800881 }
882
883 private void updateInteractiveness() {
Selim Cinek195dfc52019-05-30 19:35:05 -0700884 mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
Selim Cinekc6813462017-01-13 17:10:38 -0800885 setClickable(mInteractive);
886 setFocusable(mInteractive);
887 setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
Selim Cinekaca84c02017-04-05 16:28:56 -0700888 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Selim Cinek810bcde2016-12-14 17:29:23 -0800889 }
890
Selim Cinekc6813462017-01-13 17:10:38 -0800891 @Override
892 protected boolean isInteractive() {
893 return mInteractive;
894 }
895
Selim Cineka1d97902016-12-14 16:31:40 -0800896 public void setMaxShelfEnd(float maxShelfEnd) {
897 mMaxShelfEnd = maxShelfEnd;
898 }
899
Selim Cinek09bd29d2017-02-03 15:30:28 -0800900 public void setAnimationsEnabled(boolean enabled) {
901 mAnimationsEnabled = enabled;
Selim Cinek09bd29d2017-02-03 15:30:28 -0800902 if (!enabled) {
903 // we need to wait with enabling the animations until the first frame has passed
904 mShelfIcons.setAnimationsEnabled(false);
905 }
906 }
907
Selim Cinek9ef119c2017-03-01 15:13:36 -0800908 @Override
Adrian Roosd83e9992017-03-16 15:17:57 -0700909 public boolean hasOverlappingRendering() {
910 return false; // Shelf only uses alpha for transitions where the difference can't be seen.
911 }
912
913 @Override
Selim Cinekaca84c02017-04-05 16:28:56 -0700914 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
915 super.onInitializeAccessibilityNodeInfo(info);
916 if (mInteractive) {
917 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
918 AccessibilityNodeInfo.AccessibilityAction unlock
919 = new AccessibilityNodeInfo.AccessibilityAction(
920 AccessibilityNodeInfo.ACTION_CLICK,
921 getContext().getString(R.string.accessibility_overflow_action));
922 info.addAction(unlock);
923 }
924 }
925
926 @Override
Selim Cinek9ef119c2017-03-01 15:13:36 -0800927 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
928 int oldTop, int oldRight, int oldBottom) {
929 updateRelativeOffset();
930 }
931
Selim Cinekab9c7b22018-12-11 18:15:47 -0800932 public void onUiModeChanged() {
933 updateBackgroundColors();
934 }
935
Selim Cinek281c2022016-10-13 19:14:43 -0700936 private class ShelfState extends ExpandableViewState {
Selim Cinek49014f82016-11-04 14:55:30 -0700937 private float openedAmount;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700938 private boolean hasItemsInStableShelf;
Selim Cineka1d97902016-12-14 16:31:40 -0800939 private float maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700940
941 @Override
942 public void applyToView(View view) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700943 if (!mShowNotificationShelf) {
944 return;
945 }
946
Selim Cinek281c2022016-10-13 19:14:43 -0700947 super.applyToView(view);
Selim Cineka1d97902016-12-14 16:31:40 -0800948 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700949 setOpenedAmount(openedAmount);
Selim Cineka1d97902016-12-14 16:31:40 -0800950 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700951 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800952 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek281c2022016-10-13 19:14:43 -0700953 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800954
955 @Override
956 public void animateTo(View child, AnimationProperties properties) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700957 if (!mShowNotificationShelf) {
958 return;
959 }
960
Selim Cinek0cfbef42016-11-09 19:06:36 -0800961 super.animateTo(child, properties);
Selim Cineka1d97902016-12-14 16:31:40 -0800962 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700963 setOpenedAmount(openedAmount);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800964 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700965 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800966 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800967 }
Selim Cinek281c2022016-10-13 19:14:43 -0700968 }
969}