blob: 2cca701ef582c8fd61904f0a0ddbe2d582f6ad76 [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 Dupin60661a62018-04-12 10:50:13 -070066 private static final long SHELF_IN_TRANSLATION_DURATION = 200;
Lucas Dupinb561eda2018-04-09 17:25:04 -070067
Selim Cinek49014f82016-11-04 14:55:30 -070068 private NotificationIconContainer mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -070069 private int[] mTmp = new int[2];
70 private boolean mHideBackground;
71 private int mIconAppearTopPadding;
Lucas Dupinb561eda2018-04-09 17:25:04 -070072 private int mShelfAppearTranslation;
Lucas Dupin00be88f2019-01-03 17:50:52 -080073 private float mDarkShelfPadding;
Selim Cinek48ff9b42016-11-09 19:31:51 -080074 private int mStatusBarHeight;
75 private int mStatusBarPaddingStart;
Selim Cinekc383fd02016-10-21 15:31:26 -070076 private AmbientState mAmbientState;
77 private NotificationStackScrollLayout mHostLayout;
Selim Cinek9458b192016-10-25 19:02:42 -070078 private int mMaxLayoutHeight;
Selim Cineka686b2c2016-10-26 13:58:27 -070079 private int mPaddingBetweenElements;
Selim Cinekeccb5de2016-10-28 15:04:05 -070080 private int mNotGoneIndex;
81 private boolean mHasItemsInStableShelf;
Selim Cinek49014f82016-11-04 14:55:30 -070082 private NotificationIconContainer mCollapsedIcons;
Selim Cinek727903c2016-12-06 17:28:10 -080083 private int mScrollFastThreshold;
Selim Cinekb42698f2017-07-31 17:47:45 -070084 private int mIconSize;
Selim Cinek810bcde2016-12-14 17:29:23 -080085 private int mStatusBarState;
Selim Cineka1d97902016-12-14 16:31:40 -080086 private float mMaxShelfEnd;
Selim Cinekfcff4c62016-12-27 14:26:06 +010087 private int mRelativeOffset;
Selim Cinekc6813462017-01-13 17:10:38 -080088 private boolean mInteractive;
Selim Cinek2fce3c82017-05-08 12:38:09 -070089 private float mOpenedAmount;
90 private boolean mNoAnimationsInThisFrame;
Selim Cinek09bd29d2017-02-03 15:30:28 -080091 private boolean mAnimationsEnabled = true;
Anthony Chen9e05d462017-04-07 10:10:21 -070092 private boolean mShowNotificationShelf;
Selim Cinek515b2032017-11-15 10:20:19 -080093 private float mFirstElementRoundness;
Selim Cinek143672c2018-03-23 20:04:32 -070094 private Rect mClipRect = new Rect();
felkachangc02c3882018-07-27 15:32:31 +080095 private int mCutoutHeight;
Gus Prevase2d6f042018-10-17 15:25:30 -040096 private int mGapHeight;
Selim Cinek281c2022016-10-13 19:14:43 -070097
98 public NotificationShelf(Context context, AttributeSet attrs) {
99 super(context, attrs);
100 }
101
102 @Override
Jason Monk297c04e2018-08-23 17:16:59 -0400103 @VisibleForTesting
104 public void onFinishInflate() {
Selim Cinek281c2022016-10-13 19:14:43 -0700105 super.onFinishInflate();
Alan Viverette51efddb2017-04-05 10:00:01 -0400106 mShelfIcons = findViewById(R.id.content);
Selim Cinek49014f82016-11-04 14:55:30 -0700107 mShelfIcons.setClipChildren(false);
108 mShelfIcons.setClipToPadding(false);
109
Selim Cinek281c2022016-10-13 19:14:43 -0700110 setClipToActualHeight(false);
111 setClipChildren(false);
112 setClipToPadding(false);
Evan Lairdc987fc72017-12-15 10:14:22 -0500113 mShelfIcons.setIsStaticLayout(false);
Selim Cinek2871bef2017-11-22 08:40:00 -0800114 setBottomRoundness(1.0f, false /* animate */);
Selim Cinek281c2022016-10-13 19:14:43 -0700115 initDimens();
116 }
117
Jason Monk1fd3fc32018-08-14 17:20:09 -0400118 @Override
119 protected void onAttachedToWindow() {
120 super.onAttachedToWindow();
Beverly8fdb5332019-02-04 14:29:49 -0500121 ((SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class))
122 .addCallback(this, SysuiStatusBarStateController.RANK_SHELF);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400123 }
124
125 @Override
126 protected void onDetachedFromWindow() {
127 super.onDetachedFromWindow();
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800128 Dependency.get(StatusBarStateController.class).removeCallback(this);
Jason Monk1fd3fc32018-08-14 17:20:09 -0400129 }
130
Selim Cinekc383fd02016-10-21 15:31:26 -0700131 public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
132 mAmbientState = ambientState;
133 mHostLayout = hostLayout;
134 }
135
Selim Cinek281c2022016-10-13 19:14:43 -0700136 private void initDimens() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700137 Resources res = getResources();
138 mIconAppearTopPadding = res.getDimensionPixelSize(R.dimen.notification_icon_appear_padding);
139 mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
140 mStatusBarPaddingStart = res.getDimensionPixelOffset(R.dimen.status_bar_padding_start);
141 mPaddingBetweenElements = res.getDimensionPixelSize(R.dimen.notification_divider_height);
Lucas Dupinb561eda2018-04-09 17:25:04 -0700142 mShelfAppearTranslation = res.getDimensionPixelSize(R.dimen.shelf_appear_translation);
Lucas Dupin00be88f2019-01-03 17:50:52 -0800143 mDarkShelfPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding);
Anthony Chen9e05d462017-04-07 10:10:21 -0700144
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800145 ViewGroup.LayoutParams layoutParams = getLayoutParams();
Anthony Chen9e05d462017-04-07 10:10:21 -0700146 layoutParams.height = res.getDimensionPixelOffset(R.dimen.notification_shelf_height);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800147 setLayoutParams(layoutParams);
Anthony Chen9e05d462017-04-07 10:10:21 -0700148
149 int padding = res.getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
Selim Cinek0e8d77e2016-11-29 10:35:42 -0800150 mShelfIcons.setPadding(padding, 0, padding, 0);
Anthony Chen9e05d462017-04-07 10:10:21 -0700151 mScrollFastThreshold = res.getDimensionPixelOffset(R.dimen.scroll_fast_threshold);
152 mShowNotificationShelf = res.getBoolean(R.bool.config_showNotificationShelf);
Selim Cinekb42698f2017-07-31 17:47:45 -0700153 mIconSize = res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_icon_size);
Gus Prevase2d6f042018-10-17 15:25:30 -0400154 mGapHeight = res.getDimensionPixelSize(R.dimen.qs_notification_padding);
Anthony Chen9e05d462017-04-07 10:10:21 -0700155
156 if (!mShowNotificationShelf) {
157 setVisibility(GONE);
158 }
Selim Cinek281c2022016-10-13 19:14:43 -0700159 }
160
161 @Override
162 protected void onConfigurationChanged(Configuration newConfig) {
163 super.onConfigurationChanged(newConfig);
164 initDimens();
165 }
166
167 @Override
168 public void setDark(boolean dark, boolean fade, long delay) {
Selim Cinek281c2022016-10-13 19:14:43 -0700169 if (mDark == dark) return;
Lucas Dupin00be88f2019-01-03 17:50:52 -0800170 super.setDark(dark, fade, delay);
Adrian Roos456e0052017-04-04 16:44:29 -0700171 mShelfIcons.setDark(dark, fade, delay);
Adrian Roos03cf2582017-03-28 17:54:05 -0700172 updateInteractiveness();
Lucas Dupin00be88f2019-01-03 17:50:52 -0800173 updateOutline();
Selim Cinek281c2022016-10-13 19:14:43 -0700174 }
175
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800176 /**
177 * Alpha animation with translation played when this view is visible on AOD.
178 */
179 public void fadeInTranslating() {
180 mShelfIcons.setTranslationY(-mShelfAppearTranslation);
181 mShelfIcons.setAlpha(0);
182 mShelfIcons.animate()
183 .setInterpolator(Interpolators.DECELERATE_QUINT)
184 .translationY(0)
185 .setDuration(SHELF_IN_TRANSLATION_DURATION)
186 .start();
187 mShelfIcons.animate()
188 .alpha(1)
189 .setInterpolator(Interpolators.LINEAR)
190 .setDuration(SHELF_IN_TRANSLATION_DURATION)
191 .start();
192 }
193
Selim Cinek281c2022016-10-13 19:14:43 -0700194 @Override
195 protected View getContentView() {
Selim Cinek49014f82016-11-04 14:55:30 -0700196 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700197 }
198
Selim Cinek49014f82016-11-04 14:55:30 -0700199 public NotificationIconContainer getShelfIcons() {
200 return mShelfIcons;
Selim Cinek281c2022016-10-13 19:14:43 -0700201 }
202
203 @Override
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500204 public ExpandableViewState createExpandableViewState() {
205 return new ShelfState();
Selim Cinek281c2022016-10-13 19:14:43 -0700206 }
207
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500208 /** Update the state of the shelf. */
209 public void updateState(AmbientState ambientState) {
210 ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
211 ShelfState viewState = (ShelfState) getViewState();
Anthony Chen9e05d462017-04-07 10:10:21 -0700212 if (mShowNotificationShelf && lastView != null) {
Selim Cinek281c2022016-10-13 19:14:43 -0700213 float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
214 + ambientState.getStackTranslation();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500215 ExpandableViewState lastViewState = lastView.getViewState();
Selim Cinek281c2022016-10-13 19:14:43 -0700216 float viewEnd = lastViewState.yTranslation + lastViewState.height;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500217 viewState.copyFrom(lastViewState);
218 viewState.height = getIntrinsicHeight();
Lucas Dupinb561eda2018-04-09 17:25:04 -0700219
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500220 float awakenTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
Selim Cinek49014f82016-11-04 14:55:30 -0700221 getFullyClosedTranslation());
Lucas Dupinb561eda2018-04-09 17:25:04 -0700222 float yRatio = mAmbientState.hasPulsingNotifications() ?
223 0 : mAmbientState.getDarkAmount();
Lucas Dupin00be88f2019-01-03 17:50:52 -0800224 viewState.yTranslation = awakenTranslation + mDarkShelfPadding * yRatio;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500225 viewState.zTranslation = ambientState.getBaseZHeight();
felkachangc02c3882018-07-27 15:32:31 +0800226 // For the small display size, it's not enough to make the icon not covered by
227 // the top cutout so the denominator add the height of cutout.
228 // Totally, (getIntrinsicHeight() * 2 + mCutoutHeight) should be smaller then
229 // mAmbientState.getTopPadding().
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500230 float openedAmount = (viewState.yTranslation - getFullyClosedTranslation())
felkachangc02c3882018-07-27 15:32:31 +0800231 / (getIntrinsicHeight() * 2 + mCutoutHeight);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800232 openedAmount = Math.min(1.0f, openedAmount);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500233 viewState.openedAmount = openedAmount;
234 viewState.clipTopAmount = 0;
Selim Cinek459aee32019-02-20 11:18:56 -0800235 viewState.alpha = 1;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500236 viewState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
237 viewState.hideSensitive = false;
238 viewState.xTranslation = getTranslationX();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700239 if (mNotGoneIndex != -1) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500240 viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700241 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500242 viewState.hasItemsInStableShelf = lastViewState.inShelf;
243 viewState.hidden = !mAmbientState.isShadeExpanded()
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800244 || mAmbientState.isQsCustomizerShowing();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500245 viewState.maxShelfEnd = maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700246 } else {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500247 viewState.hidden = true;
248 viewState.location = ExpandableViewState.LOCATION_GONE;
249 viewState.hasItemsInStableShelf = false;
Selim Cinek281c2022016-10-13 19:14:43 -0700250 }
251 }
252
Selim Cinekc383fd02016-10-21 15:31:26 -0700253 /**
254 * Update the shelf appearance based on the other notifications around it. This transforms
255 * the icons from the notification area into the shelf.
256 */
257 public void updateAppearance() {
Anthony Chen9e05d462017-04-07 10:10:21 -0700258 // If the shelf should not be shown, then there is no need to update anything.
259 if (!mShowNotificationShelf) {
260 return;
261 }
262
Selim Cinek65d418e2016-11-29 15:42:34 -0800263 mShelfIcons.resetViewStates();
264 float shelfStart = getTranslationY();
Selim Cinekdb167372016-11-17 15:41:17 -0800265 float numViewsInShelf = 0.0f;
266 View lastChild = mAmbientState.getLastVisibleBackgroundChild();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700267 mNotGoneIndex = -1;
Selim Cinek49014f82016-11-04 14:55:30 -0700268 float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
269 float expandAmount = 0.0f;
Selim Cinek65d418e2016-11-29 15:42:34 -0800270 if (shelfStart >= interpolationStart) {
271 expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
Selim Cinek49014f82016-11-04 14:55:30 -0700272 expandAmount = Math.min(1.0f, expandAmount);
273 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700274 // find the first view that doesn't overlap with the shelf
Selim Cinekdb167372016-11-17 15:41:17 -0800275 int notGoneIndex = 0;
Selim Cinekec29d342017-05-05 18:31:49 -0700276 int colorOfViewBeforeLast = NO_COLOR;
Selim Cinekdb167372016-11-17 15:41:17 -0800277 boolean backgroundForceHidden = false;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500278 if (mHideBackground && !((ShelfState) getViewState()).hasItemsInStableShelf) {
Selim Cinekdb167372016-11-17 15:41:17 -0800279 backgroundForceHidden = true;
280 }
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800281 int colorTwoBefore = NO_COLOR;
282 int previousColor = NO_COLOR;
283 float transitionAmount = 0.0f;
Selim Cineka1d97902016-12-14 16:31:40 -0800284 float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
285 boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
Selim Cinekd5ab6452016-12-08 16:34:00 -0800286 || (mAmbientState.isExpansionChanging()
287 && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
Selim Cineka1d97902016-12-14 16:31:40 -0800288 boolean scrolling = currentScrollVelocity > 0;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800289 boolean expandingAnimated = mAmbientState.isExpansionChanging()
290 && !mAmbientState.isPanelTracking();
Selim Cinek65d418e2016-11-29 15:42:34 -0800291 int baseZHeight = mAmbientState.getBaseZHeight();
Selim Cinek515b2032017-11-15 10:20:19 -0800292 int backgroundTop = 0;
Selim Cinek5040f2e2019-02-14 18:22:42 -0800293 int clipTopAmount = 0;
Selim Cinek515b2032017-11-15 10:20:19 -0800294 float firstElementRoundness = 0.0f;
Gus Prevase2d6f042018-10-17 15:25:30 -0400295 ExpandableNotificationRow previousRow = null;
Rohan Shah524cf7b2018-03-15 14:40:02 -0700296
297 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
298 ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
299
Selim Cinekc383fd02016-10-21 15:31:26 -0700300 if (!(child instanceof ExpandableNotificationRow)
301 || child.getVisibility() == GONE) {
302 continue;
303 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700304
Selim Cinekc383fd02016-10-21 15:31:26 -0700305 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cineka686b2c2016-10-26 13:58:27 -0700306 float notificationClipEnd;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800307 boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight
308 || row.isPinned();
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800309 boolean isLastChild = child == lastChild;
Selim Cinek65d418e2016-11-29 15:42:34 -0800310 float rowTranslationY = row.getTranslationY();
Selim Cinek3ff9fba2017-07-20 10:47:48 -0700311 if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700312 notificationClipEnd = shelfStart + getIntrinsicHeight();
313 } else {
Selim Cineka686b2c2016-10-26 13:58:27 -0700314 notificationClipEnd = shelfStart - mPaddingBetweenElements;
Selim Cinek65d418e2016-11-29 15:42:34 -0800315 float height = notificationClipEnd - rowTranslationY;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800316 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700317 // We want the gap to close when we reached the minimum size and only shrink
318 // before
319 notificationClipEnd = Math.min(shelfStart,
Selim Cinek65d418e2016-11-29 15:42:34 -0800320 rowTranslationY + getNotificationMergeSize());
Selim Cineka686b2c2016-10-26 13:58:27 -0700321 }
322 }
Selim Cinek5040f2e2019-02-14 18:22:42 -0800323 int clipTop = updateNotificationClipHeight(row, notificationClipEnd, notGoneIndex);
324 clipTopAmount = Math.max(clipTop, clipTopAmount);
Selim Cineka1d97902016-12-14 16:31:40 -0800325 float inShelfAmount = updateIconAppearance(row, expandAmount, scrolling, scrollingFast,
Selim Cinekd5ab6452016-12-08 16:34:00 -0800326 expandingAnimated, isLastChild);
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800327 numViewsInShelf += inShelfAmount;
328 int ownColorUntinted = row.getBackgroundColorWithoutTint();
Selim Cinek65d418e2016-11-29 15:42:34 -0800329 if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
Selim Cinekdb167372016-11-17 15:41:17 -0800330 mNotGoneIndex = notGoneIndex;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800331 setTintColor(previousColor);
332 setOverrideTintColor(colorTwoBefore, transitionAmount);
333
334 } else if (mNotGoneIndex == -1) {
335 colorTwoBefore = previousColor;
336 transitionAmount = inShelfAmount;
337 }
Selim Cinekec29d342017-05-05 18:31:49 -0700338 if (isLastChild) {
339 if (colorOfViewBeforeLast == NO_COLOR) {
340 colorOfViewBeforeLast = ownColorUntinted;
341 }
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800342 row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
343 } else {
344 colorOfViewBeforeLast = ownColorUntinted;
345 row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700346 }
Selim Cinekdb167372016-11-17 15:41:17 -0800347 if (notGoneIndex != 0 || !aboveShelf) {
Selim Cinekd127d792016-11-01 19:11:41 -0700348 row.setAboveShelf(false);
349 }
Selim Cinek0fe07392017-11-09 13:26:34 -0800350 if (notGoneIndex == 0) {
351 StatusBarIconView icon = row.getEntry().expandedIcon;
352 NotificationIconContainer.IconState iconState = getIconState(icon);
Selim Cinekec0c4622019-02-13 12:33:20 -0800353 // The icon state might be null in rare cases where the notification is actually
354 // added to the layout, but not to the shelf. An example are replied messages, since
355 // they don't show up on AOD
Selim Cinekb7bafbc2017-12-21 11:33:26 -0800356 if (iconState != null && iconState.clampedAppearAmount == 1.0f) {
Selim Cinek0fe07392017-11-09 13:26:34 -0800357 // only if the first icon is fully in the shelf we want to clip to it!
Selim Cinek515b2032017-11-15 10:20:19 -0800358 backgroundTop = (int) (row.getTranslationY() - getTranslationY());
359 firstElementRoundness = row.getCurrentTopRoundness();
Selim Cinek0fe07392017-11-09 13:26:34 -0800360 }
361 }
Gus Prevase2d6f042018-10-17 15:25:30 -0400362 if (row.isFirstInSection() && previousRow != null && previousRow.isLastInSection()) {
363 // If the top of the shelf is between the view before a gap and the view after a gap
364 // then we need to adjust the shelf's top roundness.
365 float distanceToGapBottom = row.getTranslationY() - getTranslationY();
366 float distanceToGapTop = getTranslationY()
367 - (previousRow.getTranslationY() + previousRow.getActualHeight());
368 if (distanceToGapTop > 0) {
369 // We interpolate our top roundness so that it's fully rounded if we're at the
370 // bottom of the gap, and not rounded at all if we're at the top of the gap
371 // (directly up against the bottom of previousRow)
372 // Then we apply the same roundness to the bottom of previousRow so that the
373 // corners join together as the shelf approaches previousRow.
374 firstElementRoundness = (float) Math.min(1.0, distanceToGapTop / mGapHeight);
375 previousRow.setBottomRoundness(firstElementRoundness,
376 false /* don't animate */);
377 backgroundTop = (int) distanceToGapBottom;
378 }
379 }
Selim Cinekdb167372016-11-17 15:41:17 -0800380 notGoneIndex++;
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800381 previousColor = ownColorUntinted;
Gus Prevase2d6f042018-10-17 15:25:30 -0400382 previousRow = row;
Selim Cineka686b2c2016-10-26 13:58:27 -0700383 }
Rohan Shah524cf7b2018-03-15 14:40:02 -0700384 clipTransientViews();
385
Selim Cinek5040f2e2019-02-14 18:22:42 -0800386 setClipTopAmount(clipTopAmount);
Selim Cinek515b2032017-11-15 10:20:19 -0800387 setBackgroundTop(backgroundTop);
388 setFirstElementRoundness(firstElementRoundness);
Selim Cinek17e1b692016-12-02 18:19:11 -0800389 mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
Selim Cinek49014f82016-11-04 14:55:30 -0700390 mShelfIcons.calculateIconTranslations();
391 mShelfIcons.applyIconStates();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700392 for (int i = 0; i < mHostLayout.getChildCount(); i++) {
393 View child = mHostLayout.getChildAt(i);
394 if (!(child instanceof ExpandableNotificationRow)
395 || child.getVisibility() == GONE) {
396 continue;
397 }
398 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
399 updateIconClipAmount(row);
400 updateContinuousClipping(row);
401 }
Selim Cinekdb167372016-11-17 15:41:17 -0800402 boolean hideBackground = numViewsInShelf < 1.0f;
403 setHideBackground(hideBackground || backgroundForceHidden);
404 if (mNotGoneIndex == -1) {
405 mNotGoneIndex = notGoneIndex;
406 }
Selim Cinek48ff9b42016-11-09 19:31:51 -0800407 }
408
Rohan Shah524cf7b2018-03-15 14:40:02 -0700409 /**
410 * Clips transient views to the top of the shelf - Transient views are only used for
411 * disappearing views/animations and need to be clipped correctly by the shelf to ensure they
412 * don't show underneath the notification stack when something is animating and the user
413 * swipes quickly.
414 */
415 private void clipTransientViews() {
416 for (int i = 0; i < mHostLayout.getTransientViewCount(); i++) {
417 View transientView = mHostLayout.getTransientView(i);
418 if (transientView instanceof ExpandableNotificationRow) {
419 ExpandableNotificationRow transientRow = (ExpandableNotificationRow) transientView;
Selim Cinek5040f2e2019-02-14 18:22:42 -0800420 updateNotificationClipHeight(transientRow, getTranslationY(), -1);
Rohan Shah524cf7b2018-03-15 14:40:02 -0700421 } else {
422 Log.e(TAG, "NotificationShelf.clipTransientViews(): "
423 + "Trying to clip non-row transient view");
424 }
425 }
426 }
427
Selim Cinek515b2032017-11-15 10:20:19 -0800428 private void setFirstElementRoundness(float firstElementRoundness) {
429 if (mFirstElementRoundness != firstElementRoundness) {
430 mFirstElementRoundness = firstElementRoundness;
431 setTopRoundness(firstElementRoundness, false /* animate */);
Selim Cinek0fe07392017-11-09 13:26:34 -0800432 }
433 }
434
Selim Cinek6eaacf22017-09-07 18:53:17 -0700435 private void updateIconClipAmount(ExpandableNotificationRow row) {
436 float maxTop = row.getTranslationY();
Selim Cinek5040f2e2019-02-14 18:22:42 -0800437 if (getClipTopAmount() != 0) {
438 // if the shelf is clipped, lets make sure we also clip the icon
439 maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
440 }
Selim Cinek6eaacf22017-09-07 18:53:17 -0700441 StatusBarIconView icon = row.getEntry().expandedIcon;
442 float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
Selim Cinek5040f2e2019-02-14 18:22:42 -0800443 if (shelfIconPosition < maxTop && !mAmbientState.isFullyDark()) {
Selim Cinek6eaacf22017-09-07 18:53:17 -0700444 int top = (int) (maxTop - shelfIconPosition);
445 Rect clipRect = new Rect(0, top, icon.getWidth(), Math.max(top, icon.getHeight()));
446 icon.setClipBounds(clipRect);
Gus Prevasab3ad1d2018-11-01 17:11:59 -0400447 } else {
448 icon.setClipBounds(null);
Selim Cinek6eaacf22017-09-07 18:53:17 -0700449 }
450 }
451
452 private void updateContinuousClipping(final ExpandableNotificationRow row) {
453 StatusBarIconView icon = row.getEntry().expandedIcon;
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800454 boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDark();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700455 boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
456 if (needsContinuousClipping && !isContinuousClipping) {
Selim Cinek85845b82018-04-25 13:10:57 +0800457 final ViewTreeObserver observer = icon.getViewTreeObserver();
Selim Cinek6eaacf22017-09-07 18:53:17 -0700458 ViewTreeObserver.OnPreDrawListener predrawListener =
459 new ViewTreeObserver.OnPreDrawListener() {
460 @Override
461 public boolean onPreDraw() {
462 boolean animatingY = ViewState.isAnimatingY(icon);
Selim Cinek85845b82018-04-25 13:10:57 +0800463 if (!animatingY) {
Robert Snoebergered77b452018-10-12 17:22:06 +0000464 if (observer.isAlive()) {
465 observer.removeOnPreDrawListener(this);
466 }
Selim Cinek6eaacf22017-09-07 18:53:17 -0700467 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
468 return true;
469 }
470 updateIconClipAmount(row);
471 return true;
472 }
473 };
Selim Cinek85845b82018-04-25 13:10:57 +0800474 observer.addOnPreDrawListener(predrawListener);
475 icon.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
476 @Override
477 public void onViewAttachedToWindow(View v) {
478 }
479
480 @Override
481 public void onViewDetachedFromWindow(View v) {
482 if (v == icon) {
Robert Snoebergered77b452018-10-12 17:22:06 +0000483 if (observer.isAlive()) {
484 observer.removeOnPreDrawListener(predrawListener);
485 }
Selim Cinek85845b82018-04-25 13:10:57 +0800486 icon.setTag(TAG_CONTINUOUS_CLIPPING, null);
487 }
488 }
489 });
Selim Cinek6eaacf22017-09-07 18:53:17 -0700490 icon.setTag(TAG_CONTINUOUS_CLIPPING, predrawListener);
491 }
492 }
493
Selim Cinek4fc661e2019-02-19 17:42:36 -0800494 /**
495 * Update the clipping of this view.
496 * @return the amount that our own top should be clipped
497 */
Selim Cinek5040f2e2019-02-14 18:22:42 -0800498 private int updateNotificationClipHeight(ExpandableNotificationRow row,
499 float notificationClipEnd, int childIndex) {
Selim Cineka686b2c2016-10-26 13:58:27 -0700500 float viewEnd = row.getTranslationY() + row.getActualHeight();
Selim Cinekebf42342017-07-13 15:46:10 +0200501 boolean isPinned = (row.isPinned() || row.isHeadsUpAnimatingAway())
502 && !mAmbientState.isDozingAndNotPulsing(row);
Selim Cinek459aee32019-02-20 11:18:56 -0800503 boolean shouldClipOwnTop = row.showingAmbientPulsing()
Selim Cinek4fc661e2019-02-19 17:42:36 -0800504 || (mAmbientState.isPulseExpanding() && childIndex == 0);
505 if (viewEnd > notificationClipEnd && !shouldClipOwnTop
Selim Cinek7e0f9482017-05-22 20:00:56 -0700506 && (mAmbientState.isShadeExpanded() || !isPinned)) {
507 int clipBottomAmount = (int) (viewEnd - notificationClipEnd);
508 if (isPinned) {
509 clipBottomAmount = Math.min(row.getIntrinsicHeight() - row.getCollapsedHeight(),
510 clipBottomAmount);
511 }
Selim Cinek4fc661e2019-02-19 17:42:36 -0800512 row.setClipBottomAmount(clipBottomAmount);
Selim Cineka686b2c2016-10-26 13:58:27 -0700513 } else {
514 row.setClipBottomAmount(0);
515 }
Selim Cinek4fc661e2019-02-19 17:42:36 -0800516 if (shouldClipOwnTop) {
517 return (int) (viewEnd - getTranslationY());
518 } else {
519 return 0;
520 }
Selim Cineka686b2c2016-10-26 13:58:27 -0700521 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700522
Selim Cinekc8c4cf92017-09-08 15:30:09 -0700523 @Override
524 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
525 int outlineTranslation) {
526 if (!mHasItemsInStableShelf) {
527 shadowIntensity = 0.0f;
528 }
529 super.setFakeShadowIntensity(shadowIntensity, outlineAlpha, shadowYEnd, outlineTranslation);
530 }
531
Selim Cinekf9bba0b2016-11-18 15:08:21 -0800532 /**
533 * @return the icon amount how much this notification is in the shelf;
534 */
Selim Cinek2b549f42016-11-22 16:38:51 -0800535 private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800536 boolean scrolling, boolean scrollingFast, boolean expandingAnimated,
537 boolean isLastChild) {
Selim Cinek1f624952017-06-08 19:11:50 -0700538 StatusBarIconView icon = row.getEntry().expandedIcon;
539 NotificationIconContainer.IconState iconState = getIconState(icon);
540 if (iconState == null) {
541 return 0.0f;
542 }
543
Selim Cinekc383fd02016-10-21 15:31:26 -0700544 // Let calculate how much the view is in the shelf
545 float viewStart = row.getTranslationY();
Selim Cinek2b549f42016-11-22 16:38:51 -0800546 int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
547 float iconTransformDistance = getIntrinsicHeight() * 1.5f;
Selim Cineka1d97902016-12-14 16:31:40 -0800548 iconTransformDistance *= NotificationUtils.interpolate(1.f, 1.5f, expandAmount);
Selim Cinek1f624952017-06-08 19:11:50 -0700549 iconTransformDistance = Math.min(iconTransformDistance, fullHeight);
Selim Cinek938bdaa2016-11-18 16:31:09 -0800550 if (isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800551 fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
Bill Line6065e32018-06-08 17:07:13 +0800552 iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
553 - getIntrinsicHeight());
Selim Cinek938bdaa2016-11-18 16:31:09 -0800554 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800555 float viewEnd = viewStart + fullHeight;
Gus Prevas0fa58d62019-01-11 13:58:40 -0500556 // TODO: fix this check for anchor scrolling.
Selim Cinek1f624952017-06-08 19:11:50 -0700557 if (expandingAnimated && mAmbientState.getScrollY() == 0
558 && !mAmbientState.isOnKeyguard() && !iconState.isLastExpandIcon) {
559 // We are expanding animated. Because we switch to a linear interpolation in this case,
560 // the last icon may be stuck in between the shelf position and the notification
561 // position, which looks pretty bad. We therefore optimize this case by applying a
562 // shorter transition such that the icon is either fully in the notification or we clamp
563 // it into the shelf if it's close enough.
564 // We need to persist this, since after the expansion, the behavior should still be the
565 // same.
566 float position = mAmbientState.getIntrinsicPadding()
567 + mHostLayout.getPositionInLinearLayout(row);
568 int maxShelfStart = mMaxLayoutHeight - getIntrinsicHeight();
569 if (position < maxShelfStart && position + row.getIntrinsicHeight() >= maxShelfStart
570 && row.getTranslationY() < position) {
571 iconState.isLastExpandIcon = true;
572 iconState.customTransformHeight = NO_VALUE;
573 // Let's check if we're close enough to snap into the shelf
574 boolean forceInShelf = mMaxLayoutHeight - getIntrinsicHeight() - position
575 < getIntrinsicHeight();
576 if (!forceInShelf) {
577 // We are overlapping the shelf but not enough, so the icon needs to be
578 // repositioned
579 iconState.customTransformHeight = (int) (mMaxLayoutHeight
580 - getIntrinsicHeight() - position);
581 }
582 }
583 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800584 float fullTransitionAmount;
Selim Cinek01a73f92016-12-06 16:13:42 -0800585 float iconTransitionAmount;
586 float shelfStart = getTranslationY();
Selim Cinek1f624952017-06-08 19:11:50 -0700587 if (iconState.hasCustomTransformHeight()) {
588 fullHeight = iconState.customTransformHeight;
589 iconTransformDistance = iconState.customTransformHeight;
590 }
591 boolean fullyInOrOut = true;
Selim Cinekec29d342017-05-05 18:31:49 -0700592 if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || row.isInShelf())
593 && (mAmbientState.isShadeExpanded()
594 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800595 if (viewStart < shelfStart) {
Selim Cinek01a73f92016-12-06 16:13:42 -0800596 float fullAmount = (shelfStart - viewStart) / fullHeight;
Selim Cinek1f624952017-06-08 19:11:50 -0700597 fullAmount = Math.min(1.0f, fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700598 float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
Selim Cinek2b549f42016-11-22 16:38:51 -0800599 fullAmount);
Selim Cinek9458b192016-10-25 19:02:42 -0700600 interpolatedAmount = NotificationUtils.interpolate(
Selim Cinek2b549f42016-11-22 16:38:51 -0800601 interpolatedAmount, fullAmount, expandAmount);
602 fullTransitionAmount = 1.0f - interpolatedAmount;
603
Selim Cinek01a73f92016-12-06 16:13:42 -0800604 iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
605 iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
606 iconTransitionAmount = 1.0f - iconTransitionAmount;
Selim Cinek1f624952017-06-08 19:11:50 -0700607 fullyInOrOut = false;
Selim Cinekc383fd02016-10-21 15:31:26 -0700608 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800609 fullTransitionAmount = 1.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800610 iconTransitionAmount = 1.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700611 }
612 } else {
Selim Cinek2b549f42016-11-22 16:38:51 -0800613 fullTransitionAmount = 0.0f;
Selim Cinek01a73f92016-12-06 16:13:42 -0800614 iconTransitionAmount = 0.0f;
Selim Cinekc383fd02016-10-21 15:31:26 -0700615 }
Selim Cinek1f624952017-06-08 19:11:50 -0700616 if (fullyInOrOut && !expandingAnimated && iconState.isLastExpandIcon) {
617 iconState.isLastExpandIcon = false;
618 iconState.customTransformHeight = NO_VALUE;
619 }
Selim Cineka1d97902016-12-14 16:31:40 -0800620 updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount,
621 iconTransformDistance, scrolling, scrollingFast, expandingAnimated, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800622 return fullTransitionAmount;
623 }
Selim Cinekc383fd02016-10-21 15:31:26 -0700624
Selim Cinek2b549f42016-11-22 16:38:51 -0800625 private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
Selim Cineka1d97902016-12-14 16:31:40 -0800626 float fullTransitionAmount, float iconTransformDistance, boolean scrolling,
627 boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800628 StatusBarIconView icon = row.getEntry().expandedIcon;
629 NotificationIconContainer.IconState iconState = getIconState(icon);
630 if (iconState == null) {
631 return;
632 }
Selim Cinek1f624952017-06-08 19:11:50 -0700633 boolean forceInShelf = iconState.isLastExpandIcon && !iconState.hasCustomTransformHeight();
Selim Cinek2b549f42016-11-22 16:38:51 -0800634 float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800635 if (clampedAmount == fullTransitionAmount) {
Selim Cinek1f624952017-06-08 19:11:50 -0700636 iconState.noAnimations = (scrollingFast || expandingAnimated) && !forceInShelf;
Selim Cinek44d81a62017-05-08 19:45:40 -0700637 iconState.useFullTransitionAmount = iconState.noAnimations
Selim Cineka1d97902016-12-14 16:31:40 -0800638 || (!ICON_ANMATIONS_WHILE_SCROLLING && fullTransitionAmount == 0.0f && scrolling);
639 iconState.useLinearTransitionAmount = !ICON_ANMATIONS_WHILE_SCROLLING
640 && fullTransitionAmount == 0.0f && !mAmbientState.isExpansionChanging();
Selim Cinek01a73f92016-12-06 16:13:42 -0800641 iconState.translateContent = mMaxLayoutHeight - getTranslationY()
642 - getIntrinsicHeight() > 0;
Selim Cinek2b549f42016-11-22 16:38:51 -0800643 }
Selim Cinek1f624952017-06-08 19:11:50 -0700644 if (!forceInShelf && (scrollingFast || (expandingAnimated
645 && iconState.useFullTransitionAmount && !ViewState.isAnimatingY(icon)))) {
Selim Cinekd5ab6452016-12-08 16:34:00 -0800646 iconState.cancelAnimations(icon);
647 iconState.useFullTransitionAmount = true;
Selim Cinek44d81a62017-05-08 19:45:40 -0700648 iconState.noAnimations = true;
Selim Cinekd5ab6452016-12-08 16:34:00 -0800649 }
Selim Cinek1f624952017-06-08 19:11:50 -0700650 if (iconState.hasCustomTransformHeight()) {
651 iconState.useFullTransitionAmount = true;
652 }
653 if (iconState.isLastExpandIcon) {
654 iconState.translateContent = false;
655 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800656 float transitionAmount;
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800657 if (mAmbientState.isDarkAtAll() && !row.isInShelf()) {
658 transitionAmount = mAmbientState.isFullyDark() ? 1 : 0;
659 } else if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount
Selim Cineka1d97902016-12-14 16:31:40 -0800660 || iconState.useLinearTransitionAmount) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800661 transitionAmount = iconTransitionAmount;
Selim Cinek2b549f42016-11-22 16:38:51 -0800662 } else {
Selim Cineka1d97902016-12-14 16:31:40 -0800663 // We take the clamped position instead
664 transitionAmount = clampedAmount;
Selim Cinek2fce3c82017-05-08 12:38:09 -0700665 iconState.needsCannedAnimation = iconState.clampedAppearAmount != clampedAmount
666 && !mNoAnimationsInThisFrame;
Selim Cinek2b549f42016-11-22 16:38:51 -0800667 }
668 iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
669 || iconState.useFullTransitionAmount
670 ? fullTransitionAmount
671 : transitionAmount;
672 iconState.clampedAppearAmount = clampedAmount;
Selim Cinek459aee32019-02-20 11:18:56 -0800673 float contentTransformationAmount = !row.isAboveShelf()
Selim Cinek7e0f9482017-05-22 20:00:56 -0700674 && (isLastChild || iconState.translateContent)
Selim Cinek01a73f92016-12-06 16:13:42 -0800675 ? iconTransitionAmount
676 : 0.0f;
677 row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
Selim Cineka1d97902016-12-14 16:31:40 -0800678 setIconTransformationAmount(row, transitionAmount, iconTransformDistance,
Mady Mellor434180c2017-02-13 11:29:42 -0800679 clampedAmount != transitionAmount, isLastChild);
Selim Cinek2b549f42016-11-22 16:38:51 -0800680 }
681
682 private void setIconTransformationAmount(ExpandableNotificationRow row,
Mady Mellor434180c2017-02-13 11:29:42 -0800683 float transitionAmount, float iconTransformDistance, boolean usingLinearInterpolation,
684 boolean isLastChild) {
Selim Cinek2b549f42016-11-22 16:38:51 -0800685 StatusBarIconView icon = row.getEntry().expandedIcon;
686 NotificationIconContainer.IconState iconState = getIconState(icon);
687
Selim Cinekc383fd02016-10-21 15:31:26 -0700688 View rowIcon = row.getNotificationIcon();
Selim Cineka1d97902016-12-14 16:31:40 -0800689 float notificationIconPosition = row.getTranslationY() + row.getContentTranslation();
Selim Cinekf20254c2017-02-03 10:09:33 -0800690 boolean stayingInShelf = row.isInShelf() && !row.isTransformingIntoShelf();
691 if (usingLinearInterpolation && !stayingInShelf) {
Selim Cineka1d97902016-12-14 16:31:40 -0800692 // If we interpolate from the notification position, this might lead to a slightly
693 // odd interpolation, since the notification position changes as well. Let's interpolate
694 // from a fixed distance. We can only do this if we don't animate and the icon is
695 // always in the interpolated positon.
Selim Cinekf20254c2017-02-03 10:09:33 -0800696 notificationIconPosition = getTranslationY() - iconTransformDistance;
Selim Cineka1d97902016-12-14 16:31:40 -0800697 }
Selim Cinek281c2022016-10-13 19:14:43 -0700698 float notificationIconSize = 0.0f;
699 int iconTopPadding;
700 if (rowIcon != null) {
Selim Cinek875a3a12016-11-18 17:52:16 -0800701 iconTopPadding = row.getRelativeTopPadding(rowIcon);
Selim Cinek281c2022016-10-13 19:14:43 -0700702 notificationIconSize = rowIcon.getHeight();
703 } else {
704 iconTopPadding = mIconAppearTopPadding;
705 }
706 notificationIconPosition += iconTopPadding;
Selim Cinekc383fd02016-10-21 15:31:26 -0700707 float shelfIconPosition = getTranslationY() + icon.getTop();
Selim Cinekb42698f2017-07-31 17:47:45 -0700708 shelfIconPosition += (icon.getHeight() - icon.getIconScale() * mIconSize) / 2.0f;
Selim Cinek2b549f42016-11-22 16:38:51 -0800709 float iconYTranslation = NotificationUtils.interpolate(
Selim Cineka1d97902016-12-14 16:31:40 -0800710 notificationIconPosition - shelfIconPosition,
Selim Cinek2b549f42016-11-22 16:38:51 -0800711 0,
712 transitionAmount);
Selim Cinekb42698f2017-07-31 17:47:45 -0700713 float shelfIconSize = mIconSize * icon.getIconScale();
Selim Cinek2b549f42016-11-22 16:38:51 -0800714 float alpha = 1.0f;
Selim Cinek875ba9b2017-02-13 16:20:17 -0800715 boolean noIcon = !row.isShowingIcon();
716 if (noIcon) {
Selim Cinekc383fd02016-10-21 15:31:26 -0700717 // The view currently doesn't have an icon, lets transform it in!
Selim Cinekdb167372016-11-17 15:41:17 -0800718 alpha = transitionAmount;
Selim Cinekc383fd02016-10-21 15:31:26 -0700719 notificationIconSize = shelfIconSize / 2.0f;
720 }
721 // The notification size is different from the size in the shelf / statusbar
722 float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
Selim Cinek281c2022016-10-13 19:14:43 -0700723 transitionAmount);
Selim Cinekdb167372016-11-17 15:41:17 -0800724 if (iconState != null) {
Selim Cinekb42698f2017-07-31 17:47:45 -0700725 iconState.scaleX = newSize / shelfIconSize;
Selim Cinekdb167372016-11-17 15:41:17 -0800726 iconState.scaleY = iconState.scaleX;
Adrian Roos28f90c72017-05-08 17:24:26 -0700727 iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon);
Selim Cinekf38d6c32017-06-28 15:44:02 +0200728 boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
729 if (isAppearing) {
730 iconState.hidden = true;
731 iconState.iconAppearAmount = 0.0f;
732 }
Selim Cinekdb167372016-11-17 15:41:17 -0800733 iconState.alpha = alpha;
Selim Cinek2b549f42016-11-22 16:38:51 -0800734 iconState.yTranslation = iconYTranslation;
Selim Cinekf20254c2017-02-03 10:09:33 -0800735 if (stayingInShelf) {
Selim Cinekdb167372016-11-17 15:41:17 -0800736 iconState.iconAppearAmount = 1.0f;
737 iconState.alpha = 1.0f;
738 iconState.scaleX = 1.0f;
739 iconState.scaleY = 1.0f;
740 iconState.hidden = false;
741 }
Selim Cinek459aee32019-02-20 11:18:56 -0800742 if (row.isAboveShelf() || (!row.isInShelf() && (isLastChild && row.areGutsExposed()
Selim Cinek47374632017-03-17 16:07:17 -0700743 || row.getTranslationZ() > mAmbientState.getBaseZHeight()))) {
Selim Cinek5ea19572016-11-29 15:34:48 -0800744 iconState.hidden = true;
745 }
Lucas Dupin83519da2017-06-21 11:58:31 -0700746 int backgroundColor = getBackgroundColorWithoutTint();
747 int shelfColor = icon.getContrastedStaticDrawableColor(backgroundColor);
Selim Cinek875ba9b2017-02-13 16:20:17 -0800748 if (!noIcon && shelfColor != StatusBarIconView.NO_COLOR) {
Lucas Dupinb6ed63b2017-05-30 16:17:42 -0700749 int iconColor = row.getVisibleNotificationHeader().getOriginalIconColor();
750 shelfColor = NotificationUtils.interpolateColors(iconColor, shelfColor,
Selim Cinek875ba9b2017-02-13 16:20:17 -0800751 iconState.iconAppearAmount);
752 }
753 iconState.iconColor = shelfColor;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700754 }
Selim Cinek2b549f42016-11-22 16:38:51 -0800755 }
756
757 private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
758 return mShelfIcons.getIconState(icon);
Selim Cinekc383fd02016-10-21 15:31:26 -0700759 }
760
761 private float getFullyClosedTranslation() {
762 return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
Selim Cinek281c2022016-10-13 19:14:43 -0700763 }
764
Selim Cinek281c2022016-10-13 19:14:43 -0700765 public int getNotificationMergeSize() {
766 return getIntrinsicHeight();
767 }
768
769 @Override
770 public boolean hasNoContentHeight() {
771 return true;
772 }
Selim Cineka686b2c2016-10-26 13:58:27 -0700773
Selim Cinek281c2022016-10-13 19:14:43 -0700774 private void setHideBackground(boolean hideBackground) {
Selim Cinek65d418e2016-11-29 15:42:34 -0800775 if (mHideBackground != hideBackground) {
776 mHideBackground = hideBackground;
777 updateBackground();
778 updateOutline();
779 }
Selim Cinekad7fac02016-10-18 17:09:15 -0700780 }
781
782 @Override
783 protected boolean needsOutline() {
Lucas Dupin00be88f2019-01-03 17:50:52 -0800784 return !mHideBackground && !mDark && super.needsOutline();
Selim Cinek281c2022016-10-13 19:14:43 -0700785 }
786
787 @Override
788 protected boolean shouldHideBackground() {
Lucas Dupin00be88f2019-01-03 17:50:52 -0800789 return super.shouldHideBackground() || mHideBackground || mDark;
Selim Cinek281c2022016-10-13 19:14:43 -0700790 }
791
Selim Cinekfcff4c62016-12-27 14:26:06 +0100792 @Override
793 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
794 super.onLayout(changed, left, top, right, bottom);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800795 updateRelativeOffset();
Selim Cinek143672c2018-03-23 20:04:32 -0700796
797 // we always want to clip to our sides, such that nothing can draw outside of these bounds
798 int height = getResources().getDisplayMetrics().heightPixels;
799 mClipRect.set(0, -height, getWidth(), height);
800 mShelfIcons.setClipBounds(mClipRect);
Selim Cinek9ef119c2017-03-01 15:13:36 -0800801 }
802
803 private void updateRelativeOffset() {
Selim Cinek49014f82016-11-04 14:55:30 -0700804 mCollapsedIcons.getLocationOnScreen(mTmp);
Selim Cinekfcff4c62016-12-27 14:26:06 +0100805 mRelativeOffset = mTmp[0];
806 getLocationOnScreen(mTmp);
807 mRelativeOffset -= mTmp[0];
808 }
809
felkachangc02c3882018-07-27 15:32:31 +0800810 @Override
811 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
812 WindowInsets ret = super.onApplyWindowInsets(insets);
813
814 // NotificationShelf drag from the status bar and the status bar dock on the top
815 // of the display for current design so just focus on the top of ScreenDecorations.
816 // In landscape or multiple window split mode, the NotificationShelf still drag from
817 // the top and the physical notch/cutout goes to the right, left, or both side of the
818 // display so it doesn't matter for the NotificationSelf in landscape.
819 DisplayCutout displayCutout = insets.getDisplayCutout();
820 mCutoutHeight = displayCutout == null || displayCutout.getSafeInsetTop() < 0
821 ? 0 : displayCutout.getSafeInsetTop();
822
823 return ret;
824 }
825
Selim Cinekfcff4c62016-12-27 14:26:06 +0100826 private void setOpenedAmount(float openedAmount) {
Selim Cinek2fce3c82017-05-08 12:38:09 -0700827 mNoAnimationsInThisFrame = openedAmount == 1.0f && mOpenedAmount == 0.0f;
828 mOpenedAmount = openedAmount;
Lucas Dupin55c6e802018-09-27 18:07:36 -0700829 if (!mAmbientState.isPanelFullWidth() || mAmbientState.isDark()) {
Selim Cinekfcff4c62016-12-27 14:26:06 +0100830 // We don't do a transformation at all, lets just assume we are fully opened
831 openedAmount = 1.0f;
832 }
833 int start = mRelativeOffset;
Selim Cinek49014f82016-11-04 14:55:30 -0700834 if (isLayoutRtl()) {
835 start = getWidth() - start - mCollapsedIcons.getWidth();
836 }
Evan Lairdc987fc72017-12-15 10:14:22 -0500837 int width = (int) NotificationUtils.interpolate(
838 start + mCollapsedIcons.getFinalTranslationX(),
Selim Cinek49014f82016-11-04 14:55:30 -0700839 mShelfIcons.getWidth(),
felkachangc02c3882018-07-27 15:32:31 +0800840 FAST_OUT_SLOW_IN_REVERSE.getInterpolation(openedAmount));
Selim Cinek49014f82016-11-04 14:55:30 -0700841 mShelfIcons.setActualLayoutWidth(width);
Selim Cinek932005d2016-12-05 17:12:09 -0800842 boolean hasOverflow = mCollapsedIcons.hasOverflow();
843 int collapsedPadding = mCollapsedIcons.getPaddingEnd();
844 if (!hasOverflow) {
845 // we have to ensure that adding the low priority notification won't lead to an
846 // overflow
Evan Laird8cf0de42018-02-06 18:34:55 -0500847 collapsedPadding -= mCollapsedIcons.getNoOverflowExtraPadding();
Evan Lairdc987fc72017-12-15 10:14:22 -0500848 } else {
849 // Partial overflow padding will fill enough space to add extra dots
850 collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
Selim Cinek932005d2016-12-05 17:12:09 -0800851 }
852 float padding = NotificationUtils.interpolate(collapsedPadding,
Selim Cinek49014f82016-11-04 14:55:30 -0700853 mShelfIcons.getPaddingEnd(),
854 openedAmount);
855 mShelfIcons.setActualPaddingEnd(padding);
856 float paddingStart = NotificationUtils.interpolate(start,
857 mShelfIcons.getPaddingStart(), openedAmount);
858 mShelfIcons.setActualPaddingStart(paddingStart);
Selim Cinek17e1b692016-12-02 18:19:11 -0800859 mShelfIcons.setOpenedAmount(openedAmount);
Selim Cinek48ff9b42016-11-09 19:31:51 -0800860 }
861
Selim Cinek9458b192016-10-25 19:02:42 -0700862 public void setMaxLayoutHeight(int maxLayoutHeight) {
863 mMaxLayoutHeight = maxLayoutHeight;
864 }
865
Selim Cinekeccb5de2016-10-28 15:04:05 -0700866 /**
867 * @return the index of the notification at which the shelf visually resides
868 */
869 public int getNotGoneIndex() {
870 return mNotGoneIndex;
871 }
872
873 private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
Selim Cinek810bcde2016-12-14 17:29:23 -0800874 if (mHasItemsInStableShelf != hasItemsInStableShelf) {
875 mHasItemsInStableShelf = hasItemsInStableShelf;
876 updateInteractiveness();
877 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700878 }
879
880 /**
881 * @return whether the shelf has any icons in it when a potential animation has finished, i.e
882 * if the current state would be applied right now
883 */
884 public boolean hasItemsInStableShelf() {
885 return mHasItemsInStableShelf;
886 }
887
Selim Cinek49014f82016-11-04 14:55:30 -0700888 public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
889 mCollapsedIcons = collapsedIcons;
Selim Cinek9ef119c2017-03-01 15:13:36 -0800890 mCollapsedIcons.addOnLayoutChangeListener(this);
Selim Cinek49014f82016-11-04 14:55:30 -0700891 }
892
Lucas Dupin7fc9dc12019-01-03 09:19:43 -0800893 @Override
894 public void onStateChanged(int newState) {
895 mStatusBarState = newState;
Jason Monk1fd3fc32018-08-14 17:20:09 -0400896 updateInteractiveness();
Selim Cinek810bcde2016-12-14 17:29:23 -0800897 }
898
899 private void updateInteractiveness() {
Adrian Roos03cf2582017-03-28 17:54:05 -0700900 mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf
901 && !mDark;
Selim Cinekc6813462017-01-13 17:10:38 -0800902 setClickable(mInteractive);
903 setFocusable(mInteractive);
904 setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
Selim Cinekaca84c02017-04-05 16:28:56 -0700905 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
Selim Cinek810bcde2016-12-14 17:29:23 -0800906 }
907
Selim Cinekc6813462017-01-13 17:10:38 -0800908 @Override
909 protected boolean isInteractive() {
910 return mInteractive;
911 }
912
Selim Cineka1d97902016-12-14 16:31:40 -0800913 public void setMaxShelfEnd(float maxShelfEnd) {
914 mMaxShelfEnd = maxShelfEnd;
915 }
916
Selim Cinek09bd29d2017-02-03 15:30:28 -0800917 public void setAnimationsEnabled(boolean enabled) {
918 mAnimationsEnabled = enabled;
919 mCollapsedIcons.setAnimationsEnabled(enabled);
920 if (!enabled) {
921 // we need to wait with enabling the animations until the first frame has passed
922 mShelfIcons.setAnimationsEnabled(false);
923 }
924 }
925
Selim Cinek9ef119c2017-03-01 15:13:36 -0800926 @Override
Adrian Roosd83e9992017-03-16 15:17:57 -0700927 public boolean hasOverlappingRendering() {
928 return false; // Shelf only uses alpha for transitions where the difference can't be seen.
929 }
930
931 @Override
Selim Cinekaca84c02017-04-05 16:28:56 -0700932 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
933 super.onInitializeAccessibilityNodeInfo(info);
934 if (mInteractive) {
935 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
936 AccessibilityNodeInfo.AccessibilityAction unlock
937 = new AccessibilityNodeInfo.AccessibilityAction(
938 AccessibilityNodeInfo.ACTION_CLICK,
939 getContext().getString(R.string.accessibility_overflow_action));
940 info.addAction(unlock);
941 }
942 }
943
944 @Override
Selim Cinek9ef119c2017-03-01 15:13:36 -0800945 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
946 int oldTop, int oldRight, int oldBottom) {
947 updateRelativeOffset();
948 }
949
Selim Cinekab9c7b22018-12-11 18:15:47 -0800950 public void onUiModeChanged() {
951 updateBackgroundColors();
952 }
953
Selim Cinek281c2022016-10-13 19:14:43 -0700954 private class ShelfState extends ExpandableViewState {
Selim Cinek49014f82016-11-04 14:55:30 -0700955 private float openedAmount;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700956 private boolean hasItemsInStableShelf;
Selim Cineka1d97902016-12-14 16:31:40 -0800957 private float maxShelfEnd;
Selim Cinek281c2022016-10-13 19:14:43 -0700958
959 @Override
960 public void applyToView(View view) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700961 if (!mShowNotificationShelf) {
962 return;
963 }
964
Selim Cinek281c2022016-10-13 19:14:43 -0700965 super.applyToView(view);
Selim Cineka1d97902016-12-14 16:31:40 -0800966 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700967 setOpenedAmount(openedAmount);
Selim Cineka1d97902016-12-14 16:31:40 -0800968 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700969 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800970 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek281c2022016-10-13 19:14:43 -0700971 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800972
973 @Override
974 public void animateTo(View child, AnimationProperties properties) {
Anthony Chen9e05d462017-04-07 10:10:21 -0700975 if (!mShowNotificationShelf) {
976 return;
977 }
978
Selim Cinek0cfbef42016-11-09 19:06:36 -0800979 super.animateTo(child, properties);
Selim Cineka1d97902016-12-14 16:31:40 -0800980 setMaxShelfEnd(maxShelfEnd);
Selim Cinek49014f82016-11-04 14:55:30 -0700981 setOpenedAmount(openedAmount);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800982 updateAppearance();
Selim Cinekeccb5de2016-10-28 15:04:05 -0700983 setHasItemsInStableShelf(hasItemsInStableShelf);
Selim Cinek09bd29d2017-02-03 15:30:28 -0800984 mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800985 }
Selim Cinek281c2022016-10-13 19:14:43 -0700986 }
987}