diff options
46 files changed, 1651 insertions, 445 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index d8e1c895a6eb..c6568e16086c 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5577,6 +5577,24 @@ public class Notification implements Parcelable public void setRebuildStyledRemoteViews(boolean rebuild) { mRebuildStyledRemoteViews = rebuild; } + + /** + * Get the text that should be displayed in the statusBar when heads upped. This is + * usually just the app name, but may be different depending on the style. + * + * @param publicMode If true, return a text that is safe to display in public. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText(boolean publicMode) { + if (mStyle != null && !publicMode) { + CharSequence text = mStyle.getHeadsUpStatusBarText(); + if (!TextUtils.isEmpty(text)) { + return text; + } + } + return loadHeaderAppName(); + } } /** @@ -5954,6 +5972,16 @@ public class Notification implements Parcelable * @hide */ public abstract boolean areNotificationsVisiblyDifferent(Style other); + + /** + * @return the the text that should be displayed in the statusBar when heads-upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + public CharSequence getHeadsUpStatusBarText() { + return null; + } } /** @@ -6403,6 +6431,23 @@ public class Notification implements Parcelable } /** + * @return the the text that should be displayed in the statusBar when heads upped. + * If {@code null} is returned, the default implementation will be used. + * + * @hide + */ + @Override + public CharSequence getHeadsUpStatusBarText() { + CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle) + ? super.mBigContentTitle + : mConversationTitle; + if (!TextUtils.isEmpty(conversationTitle) && !hasOnlyWhiteSpaceSenders()) { + return conversationTitle; + } + return null; + } + + /** * @return the user to be displayed for any replies sent by the user */ public Person getUser() { diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index ad4d7ddfc269..ac01c4efc3ea 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3232,6 +3232,7 @@ <java-symbol type="id" name="remote_input_progress" /> <java-symbol type="id" name="remote_input_send" /> <java-symbol type="id" name="remote_input" /> + <java-symbol type="dimen" name="notification_content_margin" /> <java-symbol type="dimen" name="slice_shortcut_size" /> <java-symbol type="dimen" name="slice_icon_size" /> <java-symbol type="dimen" name="slice_padding" /> diff --git a/packages/SystemUI/res/drawable/heads_up_scrim.xml b/packages/SystemUI/res/drawable/heads_up_scrim.xml deleted file mode 100644 index 59000fcf37d2..000000000000 --- a/packages/SystemUI/res/drawable/heads_up_scrim.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<!-- - ~ Copyright (C) 2014 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> - -<shape xmlns:android="http://schemas.android.com/apk/res/android"> - <gradient - android:type="linear" - android:angle="-90" - android:startColor="#55000000" - android:endColor="#00000000" /> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml new file mode 100644 index 000000000000..bacb5c13d407 --- /dev/null +++ b/packages/SystemUI/res/layout/heads_up_status_bar_layout.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2018 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.systemui.statusbar.HeadsUpStatusBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:visibility="invisible" + android:id="@+id/heads_up_status_bar_view" + android:alpha="0" +> + <!-- This is a space just used as a layout and it's not actually displaying anything. We're + repositioning the statusbar icon to the position where this is laid out when showing this + view. --> + <Space + android:id="@+id/icon_placeholder" + android:layout_width="@dimen/status_bar_icon_drawing_size" + android:layout_height="@dimen/status_bar_icon_drawing_size" + android:layout_gravity="center_vertical" + /> + <TextView + android:id="@+id/text" + android:textAppearance="@style/TextAppearance.HeadsUpStatusBarText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:textAlignment="viewStart" + android:paddingStart="6dp" + android:layout_weight="1" + android:layout_gravity="center_vertical" + /> + +</com.android.systemui.statusbar.HeadsUpStatusBarView> diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml index 6732e6c62e8e..fa696cc1f54c 100644 --- a/packages/SystemUI/res/layout/notification_icon_area.xml +++ b/packages/SystemUI/res/layout/notification_icon_area.xml @@ -18,12 +18,14 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/notification_icon_area_inner" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="match_parent" + android:clipChildren="false"> <com.android.systemui.statusbar.phone.NotificationIconContainer android:id="@+id/notificationIcons" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentStart="true" android:gravity="center_vertical" - android:orientation="horizontal"/> + android:orientation="horizontal" + android:clipChildren="false"/> </com.android.keyguard.AlphaOptimizedLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 8c0b9bab35a0..9d336e25985f 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -3,16 +3,16 @@ ** ** Copyright 2006, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at ** -** http://www.apache.org/licenses/LICENSE-2.0 +** http://www.apache.org/licenses/LICENSE-2.0 ** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and ** limitations under the License. */ --> @@ -34,7 +34,7 @@ android:id="@+id/notification_lights_out" android:layout_width="@dimen/status_bar_icon_size" android:layout_height="match_parent" - android:paddingStart="6dip" + android:paddingStart="@dimen/status_bar_padding_start" android:paddingBottom="2dip" android:src="@drawable/ic_sysbar_lights_out_dot_small" android:scaleType="center" @@ -44,7 +44,7 @@ <LinearLayout android:id="@+id/status_bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="6dp" + android:paddingStart="@dimen/status_bar_padding_start" android:paddingEnd="8dp" android:orientation="horizontal" > @@ -53,33 +53,41 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout="@layout/operator_name" /> - - <LinearLayout + <FrameLayout android:layout_height="match_parent" android:layout_width="0dp" - android:layout_weight="1" - > - <com.android.systemui.statusbar.policy.Clock - android:id="@+id/clock" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:textAppearance="@style/TextAppearance.StatusBar.Clock" - android:singleLine="true" - android:paddingStart="@dimen/status_bar_left_clock_starting_padding" - android:paddingEnd="@dimen/status_bar_left_clock_end_padding" - android:gravity="center_vertical|start" - /> + android:layout_weight="1"> - <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and - PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). --> - <com.android.systemui.statusbar.AlphaOptimizedFrameLayout - android:id="@+id/notification_icon_area" - android:layout_width="0dp" + <include layout="@layout/heads_up_status_bar_layout" /> + + <LinearLayout android:layout_height="match_parent" - android:layout_weight="1" - android:orientation="horizontal" /> + android:layout_width="match_parent" + android:clipChildren="false" + > + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:singleLine="true" + android:paddingStart="@dimen/status_bar_left_clock_starting_padding" + android:paddingEnd="@dimen/status_bar_left_clock_end_padding" + android:gravity="center_vertical|start" + /> + + <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and + PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). --> + <com.android.systemui.statusbar.AlphaOptimizedFrameLayout + android:id="@+id/notification_icon_area" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="horizontal" + android:clipChildren="false"/> - </LinearLayout> + </LinearLayout> + </FrameLayout> <!-- Space should cover the notch (if it exists) and let other views lay out around it --> <android.widget.Space diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index ef442e539c0c..75403b91d299 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -51,14 +51,6 @@ sysui:ignoreRightInset="true" /> - <com.android.systemui.statusbar.AlphaOptimizedView - android:id="@+id/heads_up_scrim" - android:layout_width="match_parent" - android:layout_height="@dimen/heads_up_scrim_height" - android:background="@drawable/heads_up_scrim" - sysui:ignoreRightInset="true" - android:importantForAccessibility="no"/> - <FrameLayout android:id="@+id/status_bar_container" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cb3c282f703b..d65e42bc4e40 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -110,6 +110,12 @@ <!-- Side padding on the lockscreen on the side of notifications --> <dimen name="notification_side_paddings">4dp</dimen> + <!-- padding between the heads up and the statusbar --> + <dimen name="heads_up_status_bar_padding">8dp</dimen> + + <!-- heads up elevation that is added if the view is pinned --> + <dimen name="heads_up_pinned_elevation">16dp</dimen> + <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound and the notification won't use this much, but is measured with wrap_content --> <dimen name="notification_messaging_actions_min_height">196dp</dimen> @@ -451,7 +457,6 @@ <dimen name="keyguard_clock_notifications_margin">30dp</dimen> <!-- Minimum margin between clock and status bar --> <dimen name="keyguard_clock_top_margin">36dp</dimen> - <dimen name="heads_up_scrim_height">250dp</dimen> <item name="scrim_behind_alpha" format="float" type="dimen">0.62</item> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 1470dfa4d05d..20d883435aef 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -490,6 +490,10 @@ <item name="android:paddingEnd">8dp</item> </style> + <style name="TextAppearance.HeadsUpStatusBarText" + parent="@*android:style/TextAppearance.Material.Notification.Info"> + </style> + <style name="edit_theme" parent="qs_base"> <item name="android:colorBackground">?android:attr/colorSecondary</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 91edfda06261..391843ce55ba 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -100,10 +100,10 @@ public class SystemUIFactory { } public ScrimController createScrimController(LightBarController lightBarController, - ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - LockscreenWallpaper lockscreenWallpaper, Consumer<Integer> scrimVisibleListener, - DozeParameters dozeParameters, AlarmManager alarmManager) { - return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim, + ScrimView scrimBehind, ScrimView scrimInFront, LockscreenWallpaper lockscreenWallpaper, + Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters, + AlarmManager alarmManager) { + return new ScrimController(lightBarController, scrimBehind, scrimInFront, scrimVisibleListener, dozeParameters, alarmManager); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 0876507465a1..8c28af511496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -26,6 +26,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.RectF; import android.util.AttributeSet; +import android.util.MathUtils; import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; @@ -178,6 +179,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView private boolean mNeedsDimming; private int mDimmedAlpha; private boolean mBlockNextTouch; + private boolean mIsHeadsUpAnimation; + private int mHeadsUpAddStartLocation; + private float mHeadsUpLocation; + private boolean mIsAppearing; public ActivatableNotificationView(Context context, AttributeSet attrs) { super(context, attrs); @@ -204,6 +209,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView makeInactive(true /* animate */); } }, super::performClick, this::handleSlideBack, mFalsingManager::onNotificationDoubleTap); + initDimens(); + } + + private void initDimens() { + mHeadsUpAddStartLocation = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_start); + } + + @Override + public void onDensityOrFontScaleChanged() { + super.onDensityOrFontScaleChanged(); + initDimens(); } @Override @@ -745,27 +762,34 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public void performRemoveAnimation(long duration, float translationDirection, - Runnable onFinishedRunnable) { + public void performRemoveAnimation(long duration, long delay, + float translationDirection, boolean isHeadsUpAnimation, float endLocation, + Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); + mIsHeadsUpAnimation = isHeadsUpAnimation; + mHeadsUpLocation = endLocation; if (mDrawingAppearAnimation) { startAppearAnimation(false /* isAppearing */, translationDirection, - 0, duration, onFinishedRunnable); + delay, duration, onFinishedRunnable, animationListener); } else if (onFinishedRunnable != null) { onFinishedRunnable.run(); } } @Override - public void performAddAnimation(long delay, long duration) { + public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { enableAppearDrawing(true); + mIsHeadsUpAnimation = isHeadsUpAppear; + mHeadsUpLocation = mHeadsUpAddStartLocation; if (mDrawingAppearAnimation) { - startAppearAnimation(true /* isAppearing */, -1.0f, delay, duration, null); + startAppearAnimation(true /* isAppearing */, isHeadsUpAppear ? 0.0f : -1.0f, delay, + duration, null, null); } } private void startAppearAnimation(boolean isAppearing, float translationDirection, long delay, - long duration, final Runnable onFinishedRunnable) { + long duration, final Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { cancelAppearAnimation(); mAnimationTranslationY = translationDirection * getActualHeight(); if (mAppearAnimationFraction == -1.0f) { @@ -778,6 +802,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mAppearAnimationTranslation = 0; } } + mIsAppearing = isAppearing; float targetValue; if (isAppearing) { @@ -803,6 +828,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView invalidate(); } }); + if (animationListener != null) { + mAppearAnimator.addListener(animationListener); + } if (delay > 0) { // we need to apply the initial state already to avoid drawn frames in the wrong state updateAppearAnimationAlpha(); @@ -862,9 +890,21 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView / (HORIZONTAL_ANIMATION_START - HORIZONTAL_ANIMATION_END); widthFraction = Math.min(1.0f, Math.max(0.0f, widthFraction)); widthFraction = mCurrentAppearInterpolator.getInterpolation(widthFraction); - float left = (getWidth() * (0.5f - HORIZONTAL_COLLAPSED_REST_PARTIAL / 2.0f) * - widthFraction); - float right = getWidth() - left; + float startWidthFraction = HORIZONTAL_COLLAPSED_REST_PARTIAL; + if (mIsHeadsUpAnimation && !mIsAppearing) { + startWidthFraction = 0; + } + float width = MathUtils.lerp(startWidthFraction, 1.0f, 1.0f - widthFraction) + * getWidth(); + float left; + float right; + if (mIsHeadsUpAnimation) { + left = MathUtils.lerp(mHeadsUpLocation, 0, 1.0f - widthFraction); + right = left + width; + } else { + left = getWidth() * 0.5f - width / 2.0f; + right = getWidth() - left; + } // handle top animation float heightFraction = (inverseFraction - (1.0f - VERTICAL_ANIMATION_START)) / @@ -1046,6 +1086,14 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView return calculateBgColor(false /* withTint */, false /* withOverride */); } + public boolean isPinned() { + return false; + } + + public boolean isHeadsUpAnimatingAway() { + return false; + } + public interface OnActivatedListener { void onActivated(ActivatableNotificationView view); void onActivationReset(ActivatableNotificationView view); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index e8f0925dcc71..4728a1d17b39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -28,11 +28,17 @@ public class CrossFadeHelper { public static final long ANIMATION_DURATION_LENGTH = 210; public static void fadeOut(final View view, final Runnable endRunnable) { + fadeOut(view, ANIMATION_DURATION_LENGTH, 0, endRunnable); + } + + public static void fadeOut(final View view, long duration, int delay, + final Runnable endRunnable) { view.animate().cancel(); view.animate() .alpha(0f) - .setDuration(ANIMATION_DURATION_LENGTH) + .setDuration(duration) .setInterpolator(Interpolators.ALPHA_OUT) + .setStartDelay(delay) .withEndAction(new Runnable() { @Override public void run() { @@ -93,6 +99,10 @@ public class CrossFadeHelper { } public static void fadeIn(final View view) { + fadeIn(view, ANIMATION_DURATION_LENGTH, 0); + } + + public static void fadeIn(final View view, long duration, int delay) { view.animate().cancel(); if (view.getVisibility() == View.INVISIBLE) { view.setAlpha(0.0f); @@ -100,7 +110,8 @@ public class CrossFadeHelper { } view.animate() .alpha(1f) - .setDuration(ANIMATION_DURATION_LENGTH) + .setDuration(duration) + .setStartDelay(delay) .setInterpolator(Interpolators.ALPHA_IN) .withEndAction(null); if (view.hasOverlappingRendering()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index e5c5dcdb0f58..9bd8080525a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -139,6 +139,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mSensitiveHiddenInGeneral; private boolean mShowingPublicInitialized; private boolean mHideSensitiveForIntrinsicHeight; + private float mHeaderVisibleAmount = 1.0f; /** * Is this notification expanded by the system. The expansion state can be overridden by the @@ -156,8 +157,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private NotificationContentView[] mLayouts; - private int mMaxExpandHeight; - private int mHeadsUpHeight; private int mNotificationColor; private ExpansionLogger mLogger; private String mLoggingKey; @@ -534,6 +533,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView addChildNotification(row, -1); } + /** + * Set the how much the header should be visible. A value of 0 will make the header fully gone + * and a value of 1 will make the notification look just like normal. + * This is being used for heads up notifications, when they are pinned to the top of the screen + * and the header content is extracted to the statusbar. + * + * @param headerVisibleAmount the amount the header should be visible. + */ + public void setHeaderVisibleAmount(float headerVisibleAmount) { + if (mHeaderVisibleAmount != headerVisibleAmount) { + mHeaderVisibleAmount = headerVisibleAmount; + mPrivateLayout.setHeaderVisibleAmount(headerVisibleAmount); + if (mChildrenContainer != null) { + mChildrenContainer.setHeaderVisibleAmount(headerVisibleAmount); + } + notifyHeightChanged(false /* needsAnimation */); + } + } + + @Override + public float getHeaderVisibleAmount() { + return mHeaderVisibleAmount; + } + @Override public void setHeadsUpIsVisible() { super.setHeadsUpIsVisible(); @@ -722,6 +745,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } + @Override public boolean isPinned() { return mIsPinned; } @@ -741,11 +765,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mChildrenContainer.getIntrinsicHeight(); } if(mExpandedWhenPinned) { - return Math.max(getMaxExpandHeight(), mHeadsUpHeight); + return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else if (atLeastMinHeight) { - return Math.max(getCollapsedHeight(), mHeadsUpHeight); + return Math.max(getCollapsedHeight(), getHeadsUpHeight()); } else { - return mHeadsUpHeight; + return getHeadsUpHeight(); } } @@ -1034,11 +1058,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void setDismissed(boolean dismissed, boolean fromAccessibility) { - mDismissed = dismissed; + public void setDismissed(boolean fromAccessibility) { + mDismissed = true; mGroupParentWhenDismissed = mNotificationParent; mRefocusOnDismiss = fromAccessibility; mChildAfterViewWhenDismissed = null; + mEntry.icon.setDismissed(); if (isChildInGroup()) { List<ExpandableNotificationRow> notificationChildren = mNotificationParent.getNotificationChildren(); @@ -1101,6 +1126,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * @return if the view was just heads upped and is now animating away. During such a time the * layout needs to be kept consistent */ + @Override public boolean isHeadsUpAnimatingAway() { return mHeadsupDisappearRunning; } @@ -1121,7 +1147,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView groupSummary.performDismiss(fromAccessibility); } } - setDismissed(true, fromAccessibility); + setDismissed(fromAccessibility); if (isClearable()) { if (mOnDismissRunnable != null) { mOnDismissRunnable.run(); @@ -1921,9 +1947,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView if (isPinned() || mHeadsupDisappearRunning) { return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); } else if (isExpanded()) { - return Math.max(getMaxExpandHeight(), mHeadsUpHeight); + return Math.max(getMaxExpandHeight(), getHeadsUpHeight()); } else { - return Math.max(getCollapsedHeight(), mHeadsUpHeight); + return Math.max(getCollapsedHeight(), getHeadsUpHeight()); } } else if (isExpanded()) { return getMaxExpandHeight(); @@ -1997,8 +2023,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int intrinsicBefore = getIntrinsicHeight(); super.onLayout(changed, left, top, right, bottom); - updateMaxHeights(); + if (intrinsicBefore != getIntrinsicHeight()) { + notifyHeightChanged(true /* needsAnimation */); + } if (mMenuRow.getMenuView() != null) { mMenuRow.onHeightUpdate(); } @@ -2022,15 +2051,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public void updateMaxHeights() { - int intrinsicBefore = getIntrinsicHeight(); - mMaxExpandHeight = mPrivateLayout.getExpandHeight(); - mHeadsUpHeight = mPrivateLayout.getHeadsUpHeight(); - if (intrinsicBefore != getIntrinsicHeight()) { - notifyHeightChanged(true /* needsAnimation */); - } - } - @Override public void notifyHeightChanged(boolean needsAnimation) { super.notifyHeightChanged(needsAnimation); @@ -2173,7 +2193,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } public int getMaxExpandHeight() { - return mMaxExpandHeight; + return mPrivateLayout.getExpandHeight(); + } + + + private int getHeadsUpHeight() { + return mPrivateLayout.getHeadsUpHeight(); } public boolean areGutsExposed() { @@ -2273,7 +2298,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } else if (mIsSummaryWithChildren && !isGroupExpanded() && !shouldShowPublic()) { return mChildrenContainer.getMinHeight(); } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) { - return mHeadsUpHeight; + return getHeadsUpHeight(); } NotificationContentView showingLayout = getShowingLayout(); return showingLayout.getMinHeight(); @@ -2319,10 +2344,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - public boolean isMaxExpandHeightInitialized() { - return mMaxExpandHeight != 0; - } - public NotificationContentView getShowingLayout() { return shouldShowPublic() ? mPublicLayout : mPrivateLayout; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index 8bc22016efae..67268c00a4ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -276,12 +276,18 @@ public abstract class ExpandableOutlineView extends ExpandableView { setClipToOutline(mAlwaysRoundBothCorners); } - public void setTopRoundness(float topRoundness, boolean animate) { + /** + * Set the topRoundness of this view. + * @return Whether the roundness was changed. + */ + public boolean setTopRoundness(float topRoundness, boolean animate) { if (mTopRoundness != topRoundness) { mTopRoundness = topRoundness; PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness, ROUNDNESS_PROPERTIES, animate); + return true; } + return false; } protected void applyRoundness() { @@ -305,12 +311,18 @@ public abstract class ExpandableOutlineView extends ExpandableView { return mCurrentBottomRoundness * mOutlineRadius; } - public void setBottomRoundness(float bottomRoundness, boolean animate) { + /** + * Set the bottom roundness of this view. + * @return Whether the roundness was changed. + */ + public boolean setBottomRoundness(float bottomRoundness, boolean animate) { if (mBottomRoundness != bottomRoundness) { mBottomRoundness = bottomRoundness; PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness, ROUNDNESS_PROPERTIES, animate); + return true; } + return false; } protected void setBackgroundTop(int backgroundTop) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 204adc813f50..df6a9778142f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.graphics.Paint; import android.graphics.Rect; @@ -296,19 +297,24 @@ public abstract class ExpandableView extends FrameLayout { /** * Perform a remove animation on this view. - * * @param duration The duration of the remove animation. + * @param delay The delay of the animation * @param translationDirection The direction value from [-1 ... 1] indicating in which the - * animation should be performed. A value of -1 means that The - * remove animation should be performed upwards, - * such that the child appears to be going away to the top. 1 - * Should mean the opposite. + * animation should be performed. A value of -1 means that The + * remove animation should be performed upwards, + * such that the child appears to be going away to the top. 1 + * Should mean the opposite. + * @param isHeadsUpAnimation Is this a headsUp animation. + * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. + * @param animationListener An animation listener to add to the animation. */ - public abstract void performRemoveAnimation(long duration, float translationDirection, - Runnable onFinishedRunnable); + public abstract void performRemoveAnimation(long duration, + long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, + Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener); - public abstract void performAddAnimation(long delay, long duration); + public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear); /** * Set the notification appearance to be below the speed bump. @@ -390,6 +396,10 @@ public abstract class ExpandableView extends FrameLayout { } } + public float getHeaderVisibleAmount() { + return 1.0f; + } + protected boolean shouldClipToActualHeight() { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java new file mode 100644 index 000000000000..5e03fbfc1222 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.widget.TextView; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.AlphaOptimizedLinearLayout; +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; + +/** + * The view in the statusBar that contains part of the heads-up information + */ +public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout { + private int mAbsoluteStartPadding; + private int mEndMargin; + private View mIconPlaceholder; + private TextView mTextView; + private NotificationData.Entry mShowingEntry; + private Rect mLayoutedIconRect = new Rect(); + private int[] mTmpPosition = new int[2]; + private boolean mFirstLayout = true; + private boolean mPublicMode; + private int mMaxWidth; + private View mRootView; + private int mLeftInset; + private Rect mIconDrawingRect = new Rect(); + private Runnable mOnDrawingRectChangedListener; + + public HeadsUpStatusBarView(Context context) { + this(context, null); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public HeadsUpStatusBarView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + Resources res = getResources(); + mAbsoluteStartPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings) + + res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_start); + mEndMargin = res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_end); + setPaddingRelative(mAbsoluteStartPadding, 0, mEndMargin, 0); + updateMaxWidth(); + } + + private void updateMaxWidth() { + int maxWidth = getResources().getDimensionPixelSize(R.dimen.qs_panel_width); + if (maxWidth != mMaxWidth) { + // maxWidth doesn't work with fill_parent, let's manually make it at most as big as the + // notification panel + mMaxWidth = maxWidth; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mMaxWidth > 0) { + int newSize = Math.min(MeasureSpec.getSize(widthMeasureSpec), mMaxWidth); + widthMeasureSpec = MeasureSpec.makeMeasureSpec(newSize, + MeasureSpec.getMode(widthMeasureSpec)); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateMaxWidth(); + } + + @VisibleForTesting + public HeadsUpStatusBarView(Context context, View iconPlaceholder, TextView textView) { + this(context); + mIconPlaceholder = iconPlaceholder; + mTextView = textView; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mIconPlaceholder = findViewById(R.id.icon_placeholder); + mTextView = findViewById(R.id.text); + } + + public void setEntry(NotificationData.Entry entry) { + if (entry != null) { + mShowingEntry = entry; + CharSequence text = entry.headsUpStatusBarText; + if (mPublicMode) { + text = entry.headsUpStatusBarTextPublic; + } + mTextView.setText(text); + } else { + mShowingEntry = null; + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mIconPlaceholder.getLocationOnScreen(mTmpPosition); + int left = (int) (mTmpPosition[0] - getTranslationX()); + int top = mTmpPosition[1]; + int right = left + mIconPlaceholder.getWidth(); + int bottom = top + mIconPlaceholder.getHeight(); + mLayoutedIconRect.set(left, top, right, bottom); + updateDrawingRect(); + int targetPadding = mAbsoluteStartPadding + mLeftInset; + if (left != targetPadding) { + int newPadding = targetPadding - left + getPaddingStart(); + setPaddingRelative(newPadding, 0, mEndMargin, 0); + } + if (mFirstLayout) { + // we need to do the padding calculation in the first frame, so the layout specified + // our visibility to be INVISIBLE in the beginning. let's correct that and set it + // to GONE. + setVisibility(GONE); + mFirstLayout = false; + } + } + + @Override + public void setTranslationX(float translationX) { + super.setTranslationX(translationX); + updateDrawingRect(); + } + + private void updateDrawingRect() { + float oldLeft = mIconDrawingRect.left; + mIconDrawingRect.set(mLayoutedIconRect); + mIconDrawingRect.offset((int) getTranslationX(), 0); + if (oldLeft != mIconDrawingRect.left && mOnDrawingRectChangedListener != null) { + mOnDrawingRectChangedListener.run(); + } + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + mLeftInset = insets.left; + return super.fitSystemWindows(insets); + } + + public NotificationData.Entry getShowingEntry() { + return mShowingEntry; + } + + public Rect getIconDrawingRect() { + return mIconDrawingRect; + } + + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mTextView.setTextColor(DarkIconDispatcher.getTint(area, this, tint)); + } + + public void setPublicMode(boolean publicMode) { + mPublicMode = publicMode; + } + + public void setOnDrawingRectChangedListener(Runnable onDrawingRectChangedListener) { + mOnDrawingRectChangedListener = onDrawingRectChangedListener; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 73c87953cf45..f64b1bc2abe8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -602,14 +602,15 @@ public class NotificationContentView extends FrameLayout { && (mIsHeadsUp || mHeadsUpAnimatingAway) && !mContainingNotification.isOnKeyguard(); if (transitioningBetweenHunAndExpanded || pinned) { - return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight()); + return Math.min(getViewHeight(VISIBLE_TYPE_HEADSUP), + getViewHeight(VISIBLE_TYPE_EXPANDED)); } } // Size change of the expanded version if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0 && mExpandedChild != null) { - return Math.min(mContentHeightAtAnimationStart, mExpandedChild.getHeight()); + return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED)); } int hint; @@ -619,16 +620,17 @@ public class NotificationContentView extends FrameLayout { VISIBLE_TYPE_AMBIENT_SINGLELINE)) { hint = mAmbientSingleLineChild.getHeight(); } else if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) { - hint = mHeadsUpChild.getHeight(); + hint = getViewHeight(VISIBLE_TYPE_HEADSUP); } else if (mExpandedChild != null) { - hint = mExpandedChild.getHeight(); + hint = getViewHeight(VISIBLE_TYPE_EXPANDED); } else { - hint = mContractedChild.getHeight() + mContext.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_action_list_height); + hint = getViewHeight(VISIBLE_TYPE_CONTRACTED) + + mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_action_list_height); } if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) { - hint = Math.min(hint, mExpandedChild.getHeight()); + hint = Math.min(hint, getViewHeight(VISIBLE_TYPE_EXPANDED)); } return hint; } @@ -694,8 +696,8 @@ public class NotificationContentView extends FrameLayout { } private float calculateTransformationAmount() { - int startHeight = getViewForVisibleType(mTransformationStartVisibleType).getHeight(); - int endHeight = getViewForVisibleType(mVisibleType).getHeight(); + int startHeight = getViewHeight(mTransformationStartVisibleType); + int endHeight = getViewHeight(mVisibleType); int progress = Math.abs(mContentHeight - startHeight); int totalDistance = Math.abs(endHeight - startHeight); if (totalDistance == 0) { @@ -717,11 +719,23 @@ public class NotificationContentView extends FrameLayout { if (mContainingNotification.isShowingAmbient()) { return getShowingAmbientView().getHeight(); } else if (mExpandedChild != null) { - return mExpandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput); + return getViewHeight(VISIBLE_TYPE_EXPANDED) + + getExtraRemoteInputHeight(mExpandedRemoteInput); } else if (mIsHeadsUp && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) { - return mHeadsUpChild.getHeight() + getExtraRemoteInputHeight(mHeadsUpRemoteInput); + return getViewHeight(VISIBLE_TYPE_HEADSUP) + + getExtraRemoteInputHeight(mHeadsUpRemoteInput); } - return mContractedChild.getHeight(); + return getViewHeight(VISIBLE_TYPE_CONTRACTED); + } + + private int getViewHeight(int visibleType) { + View view = getViewForVisibleType(visibleType); + int height = view.getHeight(); + NotificationViewWrapper viewWrapper = getWrapperForView(view); + if (viewWrapper != null) { + height += viewWrapper.getHeaderTranslation(); + } + return height; } public int getMinHeight() { @@ -732,7 +746,7 @@ public class NotificationContentView extends FrameLayout { if (mContainingNotification.isShowingAmbient()) { return getShowingAmbientView().getHeight(); } else if (likeGroupExpanded || !mIsChildInGroup || isGroupExpanded()) { - return mContractedChild.getHeight(); + return getViewHeight(VISIBLE_TYPE_CONTRACTED); } else { return mSingleLineView.getHeight(); } @@ -1046,7 +1060,7 @@ public class NotificationContentView extends FrameLayout { private int getVisualTypeForHeight(float viewHeight) { boolean noExpandedChild = mExpandedChild == null; - if (!noExpandedChild && viewHeight == mExpandedChild.getHeight()) { + if (!noExpandedChild && viewHeight == getViewHeight(VISIBLE_TYPE_EXPANDED)) { return VISIBLE_TYPE_EXPANDED; } if (!mUserExpanding && mIsChildInGroup && !isGroupExpanded()) { @@ -1055,13 +1069,13 @@ public class NotificationContentView extends FrameLayout { if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null && !mContainingNotification.isOnKeyguard()) { - if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) { + if (viewHeight <= getViewHeight(VISIBLE_TYPE_HEADSUP) || noExpandedChild) { return VISIBLE_TYPE_HEADSUP; } else { return VISIBLE_TYPE_EXPANDED; } } else { - if (noExpandedChild || (viewHeight <= mContractedChild.getHeight() + if (noExpandedChild || (viewHeight <= getViewHeight(VISIBLE_TYPE_CONTRACTED) && (!mIsChildInGroup || isGroupExpanded() || !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) { return VISIBLE_TYPE_CONTRACTED; @@ -1616,19 +1630,19 @@ public class NotificationContentView extends FrameLayout { } public int getExpandHeight() { - View expandedChild = mExpandedChild; - if (expandedChild == null) { - expandedChild = mContractedChild; + int viewType = VISIBLE_TYPE_EXPANDED; + if (mExpandedChild == null) { + viewType = VISIBLE_TYPE_CONTRACTED; } - return expandedChild.getHeight() + getExtraRemoteInputHeight(mExpandedRemoteInput); + return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput); } public int getHeadsUpHeight() { - View headsUpChild = mHeadsUpChild; - if (headsUpChild == null) { - headsUpChild = mContractedChild; + int viewType = VISIBLE_TYPE_HEADSUP; + if (mHeadsUpChild == null) { + viewType = VISIBLE_TYPE_CONTRACTED; } - return headsUpChild.getHeight()+ getExtraRemoteInputHeight(mHeadsUpRemoteInput); + return getViewHeight(viewType) + getExtraRemoteInputHeight(mHeadsUpRemoteInput); } public void setRemoteInputVisible(boolean remoteInputVisible) { @@ -1641,4 +1655,16 @@ public class NotificationContentView extends FrameLayout { clipChildren = clipChildren && !mRemoteInputVisible; super.setClipChildren(clipChildren); } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + if (mContractedWrapper != null) { + mContractedWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + if (mHeadsUpWrapper != null) { + mHeadsUpWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + if (mExpandedWrapper != null) { + mExpandedWrapper.setHeaderVisibleAmount(headerVisibleAmount); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 58e492fffc7a..775faee7fbc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -107,6 +107,8 @@ public class NotificationData { public CharSequence remoteInputTextWhenReset; public long lastRemoteInputSent = NOT_LAUNCHED_YET; public ArraySet<Integer> mActiveAppOps = new ArraySet<>(3); + public CharSequence headsUpStatusBarText; + public CharSequence headsUpStatusBarTextPublic; public Entry(StatusBarNotification n) { this.key = n.getKey(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 4f09133303de..abcdef3e7345 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -252,7 +252,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } ExpandableNotificationRow row = (ExpandableNotificationRow) child; float notificationClipEnd; - boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight; + boolean aboveShelf = ViewState.getFinalTranslationZ(row) > baseZHeight + || row.isPinned(); boolean isLastChild = child == lastChild; float rowTranslationY = row.getTranslationY(); if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java index 0a7ee517a24a..badc40d5f01a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import android.animation.AnimatorListenerAdapter; import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -112,14 +113,16 @@ public abstract class StackScrollerDecorView extends ExpandableView { } @Override - public void performRemoveAnimation(long duration, float translationDirection, - Runnable onFinishedRunnable) { + public void performRemoveAnimation(long duration, long delay, + float translationDirection, boolean isHeadsUpAnimation, float endLocation, + Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { // TODO: Use duration performVisibilityAnimation(false); } @Override - public void performAddAnimation(long delay, long duration) { + public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) { // TODO: use delay and duration performVisibilityAnimation(true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 6cfd42fa00eb..5bb85e231f26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -27,7 +27,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.Rect; @@ -43,7 +42,6 @@ import android.util.FloatProperty; import android.util.Log; import android.util.Property; import android.util.TypedValue; -import android.view.View; import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.view.animation.Interpolator; @@ -144,6 +142,8 @@ public class StatusBarIconView extends AnimatedImageView { private ColorMatrixColorFilter mMatrixColorFilter; private boolean mIsInShelf; private Runnable mLayoutRunnable; + private boolean mDismissed; + private Runnable mOnDismissListener; public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) { this(context, slot, sbn, false); @@ -668,6 +668,19 @@ public class StatusBarIconView extends AnimatedImageView { } public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) { + setVisibleState(visibleState, animate, endRunnable, 0); + } + + /** + * Set the visibleState of this view. + * + * @param visibleState The new state. + * @param animate Should we animate? + * @param endRunnable The runnable to run at the end. + * @param duration The duration of an animation or 0 if the default should be taken. + */ + public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable, + long duration) { boolean runnableAdded = false; if (visibleState != mVisibleState) { mVisibleState = visibleState; @@ -689,7 +702,8 @@ public class StatusBarIconView extends AnimatedImageView { mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT, currentAmount, targetAmount); mIconAppearAnimator.setInterpolator(interpolator); - mIconAppearAnimator.setDuration(ANIMATION_DURATION_FAST); + mIconAppearAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST + : duration); mIconAppearAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -711,8 +725,9 @@ public class StatusBarIconView extends AnimatedImageView { if (targetAmount != currentAmount) { mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT, currentAmount, targetAmount); - mDotAnimator.setInterpolator(interpolator); - mDotAnimator.setDuration(ANIMATION_DURATION_FAST); + mDotAnimator.setInterpolator(interpolator);; + mDotAnimator.setDuration(duration == 0 ? ANIMATION_DURATION_FAST + : duration); final boolean runRunnable = !runnableAdded; mDotAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -837,6 +852,21 @@ public class StatusBarIconView extends AnimatedImageView { mLayoutRunnable = runnable; } + public void setDismissed() { + mDismissed = true; + if (mOnDismissListener != null) { + mOnDismissListener.run(); + } + } + + public boolean isDismissed() { + return mDismissed; + } + + public void setOnDismissListener(Runnable onDismissListener) { + mOnDismissListener = onDismissListener; + } + public interface OnVisibilityChangedListener { void onVisibilityChanged(int newVisibility); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java index dfcd5e60719a..251e04b01afe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java @@ -52,6 +52,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { protected final ViewInvertHelper mInvertHelper; protected final ViewTransformationHelper mTransformationHelper; + private final int mTranslationForHeader; protected int mColor; private ImageView mIcon; @@ -63,6 +64,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { private boolean mIsLowPriority; private boolean mTransformLowPriorityTitle; private boolean mShowExpandButtonAtEnd; + protected float mHeaderTranslation; protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) { super(ctx, view, row); @@ -99,6 +101,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { resolveHeaderViews(); updateInvertHelper(); addAppOpsOnClickListener(row); + mTranslationForHeader = ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin) + - ctx.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin_top); } @Override @@ -243,6 +249,19 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper { } @Override + public void setHeaderVisibleAmount(float headerVisibleAmount) { + super.setHeaderVisibleAmount(headerVisibleAmount); + mNotificationHeader.setAlpha(headerVisibleAmount); + mHeaderTranslation = (1.0f - headerVisibleAmount) * mTranslationForHeader; + mView.setTranslationY(mHeaderTranslation); + } + + @Override + public int getHeaderTranslation() { + return (int) mHeaderTranslation; + } + + @Override public NotificationHeaderView getNotificationHeader() { return mNotificationHeader; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java index f9671182ab8c..f5110a2dbc2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java @@ -179,6 +179,9 @@ public class NotificationInflater { : builder.makeAmbientNotification(); } result.packageContext = packageContext; + result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */); + result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText( + true /* showingPublic */); return result; } @@ -456,6 +459,8 @@ public class NotificationInflater { } entry.cachedAmbientContentView = result.newAmbientView; } + entry.headsUpStatusBarText = result.headsUpStatusBarText; + entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic; if (endListener != null) { endListener.onAsyncInflationFinished(row.getEntry()); } @@ -665,6 +670,8 @@ public class NotificationInflater { private View inflatedExpandedView; private View inflatedAmbientView; private View inflatedPublicView; + private CharSequence headsUpStatusBarText; + private CharSequence headsUpStatusBarTextPublic; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java index d463eae6e43f..28beb21dba4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java @@ -278,7 +278,10 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp if (mActionsContainer != null) { // We should never push the actions higher than they are in the headsup view. int constrainedContentHeight = Math.max(mContentHeight, mMinHeightHint); - mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight()); + + // We also need to compensate for any header translation, since we're always at the end. + mActionsContainer.setTranslationY(constrainedContentHeight - mView.getHeight() + - getHeaderTranslation()); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java index 17eb4c110031..873f088f8431 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java @@ -133,6 +133,10 @@ public abstract class NotificationViewWrapper implements TransformableView { return null; } + public int getHeaderTranslation() { + return 0; + } + @Override public TransformState getCurrentState(int fadingView) { return null; @@ -198,4 +202,7 @@ public abstract class NotificationViewWrapper implements TransformableView { public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { return false; } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java new file mode 100644 index 000000000000..c57680151151 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.graphics.Rect; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.statusbar.CrossFadeHelper; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +/** + * Controls the appearance of heads up notifications in the icon area and the header itself. + */ +class HeadsUpAppearanceController implements OnHeadsUpChangedListener, + DarkIconDispatcher.DarkReceiver { + public static final int CONTENT_FADE_DURATION = 110; + public static final int CONTENT_FADE_DELAY = 100; + private final NotificationIconAreaController mNotificationIconAreaController; + private final HeadsUpManagerPhone mHeadsUpManager; + private final NotificationStackScrollLayout mStackScroller; + private final HeadsUpStatusBarView mHeadsUpStatusBarView; + private final View mClockView; + private final DarkIconDispatcher mDarkIconDispatcher; + private float mExpandedHeight; + private boolean mIsExpanded; + private float mExpandFraction; + private ExpandableNotificationRow mTrackedChild; + private boolean mShown; + + public HeadsUpAppearanceController( + NotificationIconAreaController notificationIconAreaController, + HeadsUpManagerPhone headsUpManager, + View statusbarView) { + this(notificationIconAreaController, headsUpManager, + statusbarView.findViewById(R.id.heads_up_status_bar_view), + statusbarView.findViewById(R.id.notification_stack_scroller), + statusbarView.findViewById(R.id.notification_panel), + statusbarView.findViewById(R.id.clock)); + } + + @VisibleForTesting + public HeadsUpAppearanceController( + NotificationIconAreaController notificationIconAreaController, + HeadsUpManagerPhone headsUpManager, + HeadsUpStatusBarView headsUpStatusBarView, + NotificationStackScrollLayout stackScroller, + NotificationPanelView panelView, + View clockView) { + mNotificationIconAreaController = notificationIconAreaController; + mHeadsUpManager = headsUpManager; + mHeadsUpManager.addListener(this); + mHeadsUpStatusBarView = headsUpStatusBarView; + headsUpStatusBarView.setOnDrawingRectChangedListener( + () -> updateIsolatedIconLocation(true /* requireUpdate */)); + mStackScroller = stackScroller; + panelView.addTrackingHeadsUpListener(this::setTrackingHeadsUp); + panelView.setVerticalTranslationListener(this::updatePanelTranslation); + panelView.setHeadsUpAppearanceController(this); + mStackScroller.addOnExpandedHeightListener(this::setExpandedHeight); + mStackScroller.addOnLayoutChangeListener( + (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) + -> updatePanelTranslation()); + mClockView = clockView; + mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); + mDarkIconDispatcher.addDarkReceiver(this); + } + + private void updateIsolatedIconLocation(boolean requireStateUpdate) { + mNotificationIconAreaController.setIsolatedIconLocation( + mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate); + } + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + updateTopEntry(); + updateHeader(headsUp.getEntry()); + } + + public void updatePanelTranslation() { + float newTranslation = mStackScroller.getLeft() + mStackScroller.getTranslationX(); + mHeadsUpStatusBarView.setTranslationX(newTranslation); + } + + private void updateTopEntry() { + NotificationData.Entry newEntry = null; + if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) { + newEntry = mHeadsUpManager.getTopEntry(); + } + NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); + mHeadsUpStatusBarView.setEntry(newEntry); + if (newEntry != previousEntry) { + boolean animateIsolation = false; + if (newEntry == null) { + // no heads up anymore, lets start the disappear animation + + setShown(false); + animateIsolation = !mIsExpanded; + } else if (previousEntry == null) { + // We now have a headsUp and didn't have one before. Let's start the disappear + // animation + setShown(true); + animateIsolation = !mIsExpanded; + } + updateIsolatedIconLocation(false /* requireUpdate */); + mNotificationIconAreaController.showIconIsolated(newEntry == null ? null + : newEntry.icon, animateIsolation); + } + } + + private void setShown(boolean isShown) { + if (mShown != isShown) { + mShown = isShown; + if (isShown) { + mHeadsUpStatusBarView.setVisibility(View.VISIBLE); + CrossFadeHelper.fadeIn(mHeadsUpStatusBarView, CONTENT_FADE_DURATION /* duration */, + CONTENT_FADE_DELAY /* delay */); + CrossFadeHelper.fadeOut(mClockView, CONTENT_FADE_DURATION/* duration */, + 0 /* delay */, () -> mClockView.setVisibility(View.INVISIBLE)); + } else { + CrossFadeHelper.fadeIn(mClockView, CONTENT_FADE_DURATION /* duration */, + CONTENT_FADE_DELAY /* delay */); + CrossFadeHelper.fadeOut(mHeadsUpStatusBarView, CONTENT_FADE_DURATION/* duration */, + 0 /* delay */, () -> mHeadsUpStatusBarView.setVisibility(View.GONE)); + + } + } + } + + @VisibleForTesting + public boolean isShown() { + return mShown; + } + + /** + * Should the headsup status bar view be visible right now? This may be different from isShown, + * since the headsUp manager might not have notified us yet of the state change. + * + * @return if the heads up status bar view should be shown + */ + public boolean shouldBeVisible() { + return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp(); + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + updateTopEntry(); + updateHeader(headsUp.getEntry()); + } + + public void setExpandedHeight(float expandedHeight, float appearFraction) { + boolean changedHeight = expandedHeight != mExpandedHeight; + mExpandedHeight = expandedHeight; + mExpandFraction = appearFraction; + boolean isExpanded = expandedHeight > 0; + if (changedHeight) { + updateHeadsUpHeaders(); + } + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + updateTopEntry(); + } + } + + /** + * Set a headsUp to be tracked, meaning that it is currently being pulled down after being + * in a pinned state on the top. The expand animation is different in that case and we need + * to update the header constantly afterwards. + * + * @param trackedChild the tracked headsUp or null if it's not tracking anymore. + */ + public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) { + ExpandableNotificationRow previousTracked = mTrackedChild; + mTrackedChild = trackedChild; + if (previousTracked != null) { + updateHeader(previousTracked.getEntry()); + } + } + + private void updateHeadsUpHeaders() { + mHeadsUpManager.getAllEntries().forEach(entry -> { + updateHeader(entry); + }); + } + + private void updateHeader(NotificationData.Entry entry) { + ExpandableNotificationRow row = entry.row; + float headerVisibleAmount = 1.0f; + if (row.isPinned() || row == mTrackedChild) { + headerVisibleAmount = mExpandFraction; + } + row.setHeaderVisibleAmount(headerVisibleAmount); + } + + @Override + public void onDarkChanged(Rect area, float darkIntensity, int tint) { + mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint); + } + + public void setPublicMode(boolean publicMode) { + mHeadsUpStatusBarView.setPublicMode(publicMode); + updateTopEntry(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index d2cdc27d982c..fa0a774c249e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -29,6 +29,7 @@ import android.view.ViewTreeObserver; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; +import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.StatusBarState; @@ -52,12 +53,13 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, private static final boolean DEBUG = false; private final View mStatusBarWindowView; - private int mStatusBarHeight; private final NotificationGroupManager mGroupManager; private final StatusBar mBar; private final VisualStabilityManager mVisualStabilityManager; - private boolean mReleaseOnExpandFinish; + + private int mStatusBarHeight; + private int mHeadsUpInset; private boolean mTrackingHeadsUp; private HashSet<String> mSwipedOutKeys = new HashSet<>(); private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>(); @@ -101,9 +103,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, mBar = bar; mVisualStabilityManager = visualStabilityManager; - Resources resources = mContext.getResources(); - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); + initResources(); addListener(new OnHeadsUpChangedListener() { @Override @@ -114,6 +114,20 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, }); } + private void initResources() { + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); + } + + @Override + public void onDensityOrFontScaleChanged() { + super.onDensityOrFontScaleChanged(); + initResources(); + } + /////////////////////////////////////////////////////////////////////////////////////////////// // Public methods: @@ -283,10 +297,10 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, topEntry.getLocationOnScreen(mTmpTwoArray); int minX = mTmpTwoArray[0]; int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); + int height = topEntry.getIntrinsicHeight(); info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); + info.touchableRegion.set(minX, 0, maxX, mHeadsUpInset + height); } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 2bfdefe39017..903b81339869 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,6 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -102,10 +101,11 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mCollapseSnoozes = h < 0; mInitialTouchX = x; mInitialTouchY = y; - int expandedHeight = mPickedChild.getActualHeight(); - mPanel.setPanelScrimMinFraction((float) expandedHeight + int startHeight = (int) (mPickedChild.getActualHeight() + + mPickedChild.getTranslationY()); + mPanel.setPanelScrimMinFraction((float) startHeight / mPanel.getMaxPanelHeight()); - mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight); + mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight); mPanel.startExpandingFromPeek(); // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. @@ -135,7 +135,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private void setTrackingHeadsUp(boolean tracking) { mTrackingHeadsUp = tracking; mHeadsUpManager.setTrackingHeadsUp(tracking); - mPanel.setTrackingHeadsUp(tracking); + mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null); } public void notifyFling(boolean collapse) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index e1aed7a04c35..9063dea584b1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -12,9 +12,11 @@ import android.widget.FrameLayout; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.NotificationColorUtil; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.NotificationEntryManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.NotificationUtils; @@ -31,6 +33,8 @@ import java.util.function.Function; */ public class NotificationIconAreaController implements DarkReceiver { private final NotificationColorUtil mNotificationColorUtil; + private final NotificationEntryManager mEntryManager; + private final Runnable mUpdateStatusBarIcons = this::updateStatusBarIcons; private int mIconSize; private int mIconHPadding; @@ -48,6 +52,7 @@ public class NotificationIconAreaController implements DarkReceiver { mStatusBar = statusBar; mNotificationColorUtil = NotificationColorUtil.getInstance(context); mContext = context; + mEntryManager = Dependency.get(NotificationEntryManager.class); initializeNotificationAreaViews(context); } @@ -129,8 +134,8 @@ public class NotificationIconAreaController implements DarkReceiver { } protected boolean shouldShowNotificationIcon(NotificationData.Entry entry, - NotificationData notificationData, boolean showAmbient) { - if (notificationData.isAmbient(entry.key) && !showAmbient) { + boolean showAmbient, boolean hideDismissed) { + if (mEntryManager.getNotificationData().isAmbient(entry.key) && !showAmbient) { return false; } if (!StatusBar.isTopLevelChild(entry)) { @@ -139,9 +144,13 @@ public class NotificationIconAreaController implements DarkReceiver { if (entry.row.getVisibility() == View.GONE) { return false; } + if (entry.row.isDismissed() && hideDismissed) { + return false; + } // showAmbient == show in shade but not shelf - if (!showAmbient && notificationData.shouldSuppressStatusBar(entry.key)) { + if (!showAmbient && mEntryManager.getNotificationData().shouldSuppressStatusBar( + entry.key)) { return false; } @@ -151,28 +160,30 @@ public class NotificationIconAreaController implements DarkReceiver { /** * Updates the notifications with the given list of notifications to display. */ - public void updateNotificationIcons(NotificationData notificationData) { + public void updateNotificationIcons() { - updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons, - false /* showAmbient */); - updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons, - NotificationShelf.SHOW_AMBIENT_ICONS); + updateStatusBarIcons(); + updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons, + NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */); applyNotificationIconsTint(); } + private void updateStatusBarIcons() { + updateIconsForLayout(entry -> entry.icon, mNotificationIcons, + false /* showAmbient */, true /* hideDismissed */); + } + /** * Updates the notification icons for a host layout. This will ensure that the notification * host layout will have the same icons like the ones in here. - * - * @param notificationData the notification data to look up which notifications are relevant * @param function A function to look up an icon view based on an entry * @param hostLayout which layout should be updated * @param showAmbient should ambient notification icons be shown + * @param hideDismissed should dismissed icons be hidden */ - private void updateIconsForLayout(NotificationData notificationData, - Function<NotificationData.Entry, StatusBarIconView> function, - NotificationIconContainer hostLayout, boolean showAmbient) { + private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function, + NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed) { ArrayList<StatusBarIconView> toShow = new ArrayList<>( mNotificationScrollLayout.getChildCount()); @@ -181,7 +192,7 @@ public class NotificationIconAreaController implements DarkReceiver { View view = mNotificationScrollLayout.getChildAt(i); if (view instanceof ExpandableNotificationRow) { NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry(); - if (shouldShowNotificationIcon(ent, notificationData, showAmbient)) { + if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed)) { toShow.add(function.apply(ent)); } } @@ -243,10 +254,13 @@ public class NotificationIconAreaController implements DarkReceiver { final FrameLayout.LayoutParams params = generateIconLayoutParams(); for (int i = 0; i < toShow.size(); i++) { - View v = toShow.get(i); + StatusBarIconView v = toShow.get(i); // The view might still be transiently added if it was just removed and added again hostLayout.removeTransientView(v); if (v.getParent() == null) { + if (hideDismissed) { + v.setOnDismissListener(mUpdateStatusBarIcons); + } hostLayout.addView(v, i, params); } } @@ -296,4 +310,12 @@ public class NotificationIconAreaController implements DarkReceiver { mNotificationIcons.setDark(dark, false, 0); mShelfIcons.setDark(dark, false, 0); } + + public void showIconIsolated(StatusBarIconView icon, boolean animated) { + mNotificationIcons.showIconIsolated(icon, animated); + } + + public void setIsolatedIconLocation(Rect iconDrawingRect, boolean requireStateUpdate) { + mNotificationIcons.setIsolatedIconLocation(iconDrawingRect, requireStateUpdate); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index b29ac9067556..55174349cc53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -16,17 +16,17 @@ package com.android.systemui.statusbar.phone; +import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DURATION; +import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY; + import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.Icon; -import android.os.AsyncTask; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.support.v4.util.ArrayMap; -import android.support.v4.util.ArraySet; import android.util.AttributeSet; import android.view.View; @@ -100,6 +100,33 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { } }.setDuration(200).setDelay(50); + /** + * The animation property used for all icons that were not isolated, when the isolation ends. + * This just fades the alpha and doesn't affect the movement and has a delay. + */ + private static final AnimationProperties UNISOLATION_PROPERTY_OTHERS + = new AnimationProperties() { + private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); + + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + }.setDuration(CONTENT_FADE_DURATION); + + /** + * The animation property used for the icon when its isolation ends. + * This animates the translation back to the right position. + */ + private static final AnimationProperties UNISOLATION_PROPERTY = new AnimationProperties() { + private AnimationFilter mAnimationFilter = new AnimationFilter().animateX(); + + @Override + public AnimationFilter getAnimationFilter() { + return mAnimationFilter; + } + }.setDuration(CONTENT_FADE_DURATION); + public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5; public static final int MAX_STATIC_ICONS = 4; private static final int MAX_DOTS = 3; @@ -127,6 +154,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private float mVisualOverflowStart; // Keep track of overflow in range [0, 3] private int mNumDots; + private StatusBarIconView mIsolatedIcon; + private Rect mIsolatedIconLocation; + private int[] mAbsolutePosition = new int[2]; + private View mIsolatedIconForAnimation; public NotificationIconContainer(Context context, AttributeSet attrs) { @@ -196,13 +227,18 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconSize = child.getWidth(); } } + getLocationOnScreen(mAbsolutePosition); if (mIsStaticLayout) { - resetViewStates(); - calculateIconTranslations(); - applyIconStates(); + updateState(); } } + private void updateState() { + resetViewStates(); + calculateIconTranslations(); + applyIconStates(); + } + public void applyIconStates() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -214,6 +250,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAddAnimationStartIndex = -1; mCannedAnimationStartIndex = -1; mDisallowNextAnimation = false; + mIsolatedIconForAnimation = null; } @Override @@ -281,8 +318,10 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mIconStates.remove(child); if (!isReplacingIcon) { addTransientView(icon, 0); + boolean isIsolatedIcon = child == mIsolatedIcon; icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */, - () -> removeTransientView(icon)); + () -> removeTransientView(icon), + isIsolatedIcon ? CONTENT_FADE_DURATION : 0); } } } @@ -306,7 +345,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { View view = getChildAt(i); ViewState iconState = mIconStates.get(view); iconState.initFrom(view); - iconState.alpha = 1.0f; + iconState.alpha = mIsolatedIcon == null || view == mIsolatedIcon ? 1.0f : 0.0f; iconState.hidden = false; } } @@ -402,6 +441,16 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); } } + if (mIsolatedIcon != null) { + IconState iconState = mIconStates.get(mIsolatedIcon); + if (iconState != null) { + // Most of the time the icon isn't yet added when this is called but only happening + // later + iconState.xTranslation = mIsolatedIconLocation.left - mAbsolutePosition[0] + - (1 - mIsolatedIcon.getIconScale()) * mIsolatedIcon.getWidth() / 2.0f; + iconState.visibleState = StatusBarIconView.STATE_ICON; + } + } } private float getLayoutEnd() { @@ -573,6 +622,21 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mReplacingIcons = replacingIcons; } + public void showIconIsolated(StatusBarIconView icon, boolean animated) { + if (animated) { + mIsolatedIconForAnimation = icon != null ? icon : mIsolatedIcon; + } + mIsolatedIcon = icon; + updateState(); + } + + public void setIsolatedIconLocation(Rect isolatedIconLocation, boolean requireUpdate) { + mIsolatedIconLocation = isolatedIconLocation; + if (requireUpdate) { + updateState(); + } + } + public class IconState extends ViewState { public static final int NO_VALUE = NotificationIconContainer.NO_VALUE; public float iconAppearAmount = 1.0f; @@ -646,6 +710,18 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { animationProperties.setDuration(CANNED_ANIMATION_DURATION); animate = true; } + if (mIsolatedIconForAnimation != null) { + if (view == mIsolatedIconForAnimation) { + animationProperties = UNISOLATION_PROPERTY; + animationProperties.setDelay( + mIsolatedIcon != null ? CONTENT_FADE_DELAY : 0); + } else { + animationProperties = UNISOLATION_PROPERTY_OTHERS; + animationProperties.setDelay( + mIsolatedIcon == null ? CONTENT_FADE_DELAY : 0); + } + animate = true; + } } icon.setVisibleState(visibleState, animationsAllowed); icon.setIconColor(iconColor, needsCannedAnimation && animationsAllowed); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 2711d7ac7a89..53f2690b5c78 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -74,7 +74,9 @@ import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, @@ -243,6 +245,10 @@ public class NotificationPanelView extends PanelView implements private float mExpandOffset; private boolean mHideIconsDuringNotificationLaunch = true; private int mStackScrollerMeasuringPass; + private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners + = new ArrayList<>(); + private Runnable mVerticalTranslationListener; + private HeadsUpAppearanceController mHeadsUpAppearanceController; public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -269,6 +275,7 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mNotificationStackScroller.setOverscrollTopChangedListener(this); mNotificationStackScroller.setOnEmptySpaceClickListener(this); + addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp); mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); mLastOrientation = getResources().getConfiguration().orientation; @@ -1780,11 +1787,19 @@ public class NotificationPanelView extends PanelView implements mQsExpandImmediate = false; mTwoFingerQsExpandPossible = false; mIsExpansionFromHeadsUp = false; - mNotificationStackScroller.setTrackingHeadsUp(false); + notifyListenersTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); } + private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) { + for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) { + Consumer<ExpandableNotificationRow> listener + = mTrackingHeadsUpListeners.get(i); + listener.accept(pickedChild); + } + } + private void setListening(boolean listening) { mKeyguardStatusBar.setListening(listening); if (mQs == null) return; @@ -2351,9 +2366,9 @@ public class NotificationPanelView extends PanelView implements this); } - public void setTrackingHeadsUp(boolean tracking) { - if (tracking) { - mNotificationStackScroller.setTrackingHeadsUp(true); + public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { + if (pickedChild != null) { + notifyListenersTrackingHeadsUp(pickedChild); mExpandingFromHeadsUp = true; } // otherwise we update the state when the expansion is finished @@ -2400,6 +2415,9 @@ public class NotificationPanelView extends PanelView implements protected void setVerticalPanelTranslation(float translation) { mNotificationStackScroller.setTranslationX(translation); mQsFrame.setTranslationX(translation); + if (mVerticalTranslationListener != null) { + mVerticalTranslationListener.run(); + } } protected void updateExpandedHeight(float expandedHeight) { @@ -2555,6 +2573,10 @@ public class NotificationPanelView extends PanelView implements if (mLaunchingNotification) { return mHideIconsDuringNotificationLaunch; } + if (mHeadsUpAppearanceController != null + && mHeadsUpAppearanceController.shouldBeVisible()) { + return false; + } return !isFullWidth() || !mShowIconsWhenExpanded; } @@ -2696,4 +2718,17 @@ public class NotificationPanelView extends PanelView implements } } } + + public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) { + mTrackingHeadsUpListeners.add(listener); + } + + public void setVerticalTranslationListener(Runnable verticalTranslationListener) { + mVerticalTranslationListener = verticalTranslationListener; + } + + public void setHeadsUpAppearanceController( + HeadsUpAppearanceController headsUpAppearanceController) { + mHeadsUpAppearanceController = headsUpAppearanceController; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 739d8d5c2c67..f8ae7f761052 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -49,7 +49,6 @@ import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; -import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.ViewState; import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.DelayedWakeLock; @@ -63,8 +62,8 @@ import java.util.function.Consumer; * Controls both the scrim behind the notifications and in front of the notifications (when a * security method gets shown). */ -public class ScrimController implements ViewTreeObserver.OnPreDrawListener, - OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable { +public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnColorsChangedListener, + Dumpable { private static final String TAG = "ScrimController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -106,7 +105,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private final Context mContext; protected final ScrimView mScrimBehind; protected final ScrimView mScrimInFront; - private final View mHeadsUpScrim; private final LightBarController mLightBarController; private final UnlockMethodCache mUnlockMethodCache; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @@ -140,9 +138,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private int mCurrentInFrontTint; private int mCurrentBehindTint; private boolean mWallpaperVisibilityTimedOut; - private int mPinnedHeadsUpCount; - private float mTopHeadsUpDragAmount; - private View mDraggedHeadsUpView; private int mScrimsVisibility; private final Consumer<Integer> mScrimVisibleListener; private boolean mBlankScreen; @@ -161,11 +156,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private boolean mKeyguardOccluded; public ScrimController(LightBarController lightBarController, ScrimView scrimBehind, - ScrimView scrimInFront, View headsUpScrim, Consumer<Integer> scrimVisibleListener, + ScrimView scrimInFront, Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters, AlarmManager alarmManager) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; - mHeadsUpScrim = headsUpScrim; mScrimVisibleListener = scrimVisibleListener; mContext = scrimBehind.getContext(); mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); @@ -196,7 +190,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } mState = ScrimState.UNINITIALIZED; - updateHeadsUpScrim(false); updateScrims(); } @@ -363,10 +356,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return; } - if (mPinnedHeadsUpCount != 0) { - updateHeadsUpScrim(false); - } - setOrAdaptCurrentAnimation(mScrimBehind); setOrAdaptCurrentAnimation(mScrimInFront); } @@ -610,8 +599,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return mCurrentInFrontAlpha; } else if (scrim == mScrimBehind) { return mCurrentBehindAlpha; - } else if (scrim == mHeadsUpScrim) { - return calculateHeadsUpAlpha(); } else { throw new IllegalArgumentException("Unknown scrim view"); } @@ -622,8 +609,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return mCurrentInFrontTint; } else if (scrim == mScrimBehind) { return mCurrentBehindTint; - } else if (scrim == mHeadsUpScrim) { - return Color.TRANSPARENT; } else { throw new IllegalArgumentException("Unknown scrim view"); } @@ -670,40 +655,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mScrimBehind.setDrawAsSrc(asSrc); } - @Override - public void onHeadsUpPinnedModeChanged(boolean inPinnedMode) { - } - - @Override - public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { - mPinnedHeadsUpCount++; - updateHeadsUpScrim(true); - } - - @Override - public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { - mPinnedHeadsUpCount--; - if (headsUp == mDraggedHeadsUpView) { - mDraggedHeadsUpView = null; - mTopHeadsUpDragAmount = 0.0f; - } - updateHeadsUpScrim(true); - } - - @Override - public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { - } - - private void updateHeadsUpScrim(boolean animate) { - if (animate) { - mAnimationDuration = ANIMATION_DURATION; - cancelAnimator((ValueAnimator) mHeadsUpScrim.getTag(TAG_KEY_ANIM)); - startScrimAnimation(mHeadsUpScrim, mHeadsUpScrim.getAlpha()); - } else { - setOrAdaptCurrentAnimation(mHeadsUpScrim); - } - } - @VisibleForTesting void setOnAnimationFinished(Runnable onAnimationFinished) { mOnAnimationFinished = onAnimationFinished; @@ -810,33 +761,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return Handler.getMain(); } - /** - * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means - * the heads up is in its resting space and 1 means it's fully dragged out. - * - * @param draggedHeadsUpView the dragged view - * @param topHeadsUpDragAmount how far is it dragged - */ - public void setTopHeadsUpDragAmount(View draggedHeadsUpView, float topHeadsUpDragAmount) { - mTopHeadsUpDragAmount = topHeadsUpDragAmount; - mDraggedHeadsUpView = draggedHeadsUpView; - updateHeadsUpScrim(false); - } - - private float calculateHeadsUpAlpha() { - float alpha; - if (mPinnedHeadsUpCount >= 2) { - alpha = 1.0f; - } else if (mPinnedHeadsUpCount == 0) { - alpha = 0.0f; - } else { - alpha = 1.0f - mTopHeadsUpDragAmount; - } - float expandFactor = (1.0f - mExpansionFraction); - expandFactor = Math.max(expandFactor, 0.0f); - return alpha * expandFactor; - } - public void setExcludedBackgroundArea(Rect area) { mScrimBehind.setExcludedArea(area); } @@ -851,13 +775,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mScrimBehind.setChangeRunnable(changeRunnable); } - public void onDensityOrFontScaleChanged() { - ViewGroup.LayoutParams layoutParams = mHeadsUpScrim.getLayoutParams(); - layoutParams.height = mHeadsUpScrim.getResources().getDimensionPixelSize( - R.dimen.heads_up_scrim_height); - mHeadsUpScrim.setLayoutParams(layoutParams); - } - public void setCurrentUser(int currentUser) { // Don't care in the base class. } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 7422a4343cb4..099de1932c21 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -189,6 +189,7 @@ import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.GestureRecorder; +import com.android.systemui.statusbar.HeadsUpStatusBarView; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NotificationData; @@ -602,6 +603,7 @@ public class StatusBar extends SystemUI implements DemoMode, private NavigationBarFragment mNavigationBar; private View mNavigationBarView; protected ActivityLaunchAnimator mActivityLaunchAnimator; + private HeadsUpAppearanceController mHeadsUpAppearanceController; @Override public void start() { @@ -807,6 +809,8 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarView.setPanel(mNotificationPanel); mStatusBarView.setScrimController(mScrimController); mStatusBarView.setBouncerShowing(mBouncerShowing); + mHeadsUpAppearanceController = new HeadsUpAppearanceController( + mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow); setAreThereNotifications(); checkBarModes(); }).getFragmentManager() @@ -899,9 +903,8 @@ public class StatusBar extends SystemUI implements DemoMode, ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind); ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front); - View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim); mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController, - scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper, + scrimBehind, scrimInFront, mLockscreenWallpaper, scrimsVisible -> { if (mStatusBarWindowManager != null) { mStatusBarWindowManager.setScrimsVisibility(scrimsVisible); @@ -916,7 +919,6 @@ public class StatusBar extends SystemUI implements DemoMode, mBackdrop.setOnVisibilityChangedRunnable(runnable); runnable.run(); } - mHeadsUpManager.addListener(mScrimController); mStackScroller.setScrimController(mScrimController); mDozeScrimController = new DozeScrimController(mScrimController, context); @@ -1073,7 +1075,6 @@ public class StatusBar extends SystemUI implements DemoMode, mReinflateNotificationsOnUserSwitched = true; } // end old BaseStatusBar.onDensityOrFontScaleChanged(). - mScrimController.onDensityOrFontScaleChanged(); // TODO: Remove this. if (mBrightnessMirrorController != null) { mBrightnessMirrorController.onDensityOrFontScaleChanged(); @@ -1087,6 +1088,7 @@ public class StatusBar extends SystemUI implements DemoMode, mKeyguardUserSwitcher.onDensityOrFontScaleChanged(); } mNotificationIconAreaController.onDensityOrFontScaleChanged(mContext); + mHeadsUpManager.onDensityOrFontScaleChanged(); reevaluateStyles(); } @@ -1384,8 +1386,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateQsExpansionEnabled(); // Let's also update the icons - mNotificationIconAreaController.updateNotificationIcons( - mEntryManager.getNotificationData()); + mNotificationIconAreaController.updateNotificationIcons(); } @Override @@ -1991,7 +1992,7 @@ public class StatusBar extends SystemUI implements DemoMode, mVisualStabilityManager.setPanelExpanded(isExpanded); if (isExpanded && getBarState() != StatusBarState.KEYGUARD) { if (DEBUG) { - Log.v(TAG, "clearing notification effects from setPanelExpanded"); + Log.v(TAG, "clearing notification effects from setExpandedHeight"); } clearNotificationEffects(); } @@ -2813,7 +2814,7 @@ public class StatusBar extends SystemUI implements DemoMode, public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); - entry.row.updateMaxHeights(); + entry.row.notifyHeightChanged(true /* needsAnimation */); } public void lockScrollTo(NotificationData.Entry entry) { mStackScroller.lockScrollTo(entry.row); @@ -3833,6 +3834,9 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStackScroller == null) return; boolean onKeyguard = mState == StatusBarState.KEYGUARD; boolean publicMode = mLockscreenUserManager.isAnyProfilePublicMode(); + if (mHeadsUpAppearanceController != null) { + mHeadsUpAppearanceController.setPublicMode(publicMode); + } mStackScroller.setHideSensitive(publicMode, goingToFullShade); mStackScroller.setDimmed(onKeyguard, fromShadeLocked /* animate */); mStackScroller.setExpandingEnabled(!onKeyguard); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 040d7ec32ecc..aeda55a8bf8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -443,6 +443,9 @@ public class HeadsUpManager { entry.reset(); } + public void onDensityOrFontScaleChanged() { + } + /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 53377d955722..c26568ea00b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -21,12 +21,12 @@ import android.util.Property; import android.view.View; import java.util.ArrayList; -import java.util.Set; /** * Filters the animations for only a certain type of properties. */ public class AnimationFilter { + public static final int NO_DELAY = -1; boolean animateAlpha; boolean animateX; boolean animateY; @@ -40,7 +40,7 @@ public class AnimationFilter { public boolean animateShadowAlpha; boolean hasDelays; boolean hasGoToFullShadeEvent; - boolean hasHeadsUpDisappearClickEvent; + long customDelay; private ArraySet<Property> mAnimatedProperties = new ArraySet<>(); public AnimationFilter animateAlpha() { @@ -129,8 +129,14 @@ public class AnimationFilter { hasGoToFullShadeEvent = true; } if (ev.animationType == NotificationStackScrollLayout.AnimationEvent + .ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { + customDelay = StackStateAnimator.ANIMATION_DELAY_HEADS_UP; + } else if (ev.animationType == NotificationStackScrollLayout.AnimationEvent .ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { - hasHeadsUpDisappearClickEvent = true; + // We need both timeouts when clicking, one to delay it and one for the animation + // to look nice + customDelay = StackStateAnimator.ANIMATION_DELAY_HEADS_UP_CLICKED + + StackStateAnimator.ANIMATION_DELAY_HEADS_UP; } } } @@ -165,7 +171,7 @@ public class AnimationFilter { animateHideSensitive = false; hasDelays = false; hasGoToFullShadeEvent = false; - hasHeadsUpDisappearClickEvent = false; + customDelay = NO_DELAY; mAnimatedProperties.clear(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java index 3bf7d892ea0e..a7925aa003e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java @@ -233,7 +233,8 @@ public class ExpandableViewState extends ViewState { expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay); if (properties.wasAdded(child) && !hidden) { - expandableView.performAddAnimation(properties.delay, properties.duration); + expandableView.performAddAnimation(properties.delay, properties.duration, + false /* isHeadsUpAppear */); } if (!expandableView.isInShelf() && this.inShelf) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java index 05c0099359ab..59ce0ca3b3cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java @@ -23,6 +23,11 @@ import android.view.animation.PathInterpolator; * An interpolator specifically designed for the appear animation of heads up notifications. */ public class HeadsUpAppearInterpolator extends PathInterpolator { + + private static float X1 = 250f; + private static float X2 = 200f; + private static float XTOT = (X1 + X2);; + public HeadsUpAppearInterpolator() { super(getAppearPath()); } @@ -30,22 +35,18 @@ public class HeadsUpAppearInterpolator extends PathInterpolator { private static Path getAppearPath() { Path path = new Path(); path.moveTo(0, 0); - float x1 = 250f; - float x2 = 150f; - float x3 = 100f; float y1 = 90f; - float y2 = 78f; - float y3 = 80f; - float xTot = (x1 + x2 + x3); - path.cubicTo(x1 * 0.9f / xTot, 0f, - x1 * 0.8f / xTot, y1 / y3, - x1 / xTot , y1 / y3); - path.cubicTo((x1 + x2 * 0.4f) / xTot, y1 / y3, - (x1 + x2 * 0.2f) / xTot, y2 / y3, - (x1 + x2) / xTot, y2 / y3); - path.cubicTo((x1 + x2 + x3 * 0.4f) / xTot, y2 / y3, - (x1 + x2 + x3 * 0.2f) / xTot, 1f, - 1f, 1f); + float y2 = 80f; + path.cubicTo(X1 * 0.8f / XTOT, y1 / y2, + X1 * 0.8f / XTOT, y1 / y2, + X1 / XTOT, y1 / y2); + path.cubicTo((X1 + X2 * 0.4f) / XTOT, y1 / y2, + (X1 + X2 * 0.2f) / XTOT, 1.0f, + 1.0f , 1.0f); return path; } + + public static float getFractionUntilOvershoot() { + return X1 / XTOT; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index ac2a1e1e041f..e5ab712e9bdd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -102,6 +102,9 @@ public class NotificationChildrenContainer extends ViewGroup { private boolean mShowDividersWhenExpanded; private boolean mHideDividersDuringExpand; + private int mTranslationForHeader; + private int mCurrentHeaderTranslation = 0; + private float mHeaderVisibleAmount = 1.0f; public NotificationChildrenContainer(Context context) { this(context, null); @@ -142,6 +145,9 @@ public class NotificationChildrenContainer extends ViewGroup { res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); mHideDividersDuringExpand = res.getBoolean(R.bool.config_hideDividersDuringExpand); + mTranslationForHeader = res.getDimensionPixelSize( + com.android.internal.R.dimen.notification_content_margin) + - mNotificationHeaderMargin; } @Override @@ -486,7 +492,7 @@ public class NotificationChildrenContainer extends ViewGroup { if (showingAsLowPriority()) { return mNotificationHeaderLowPriority.getHeight(); } - int intrinsicHeight = mNotificationHeaderMargin; + int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; int visibleChildren = 0; int childCount = mChildren.size(); boolean firstChild = true; @@ -541,7 +547,7 @@ public class NotificationChildrenContainer extends ViewGroup { public void getState(StackScrollState resultState, ExpandableViewState parentState, AmbientState ambientState) { int childCount = mChildren.size(); - int yPosition = mNotificationHeaderMargin; + int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation; boolean firstChild = true; int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); int lastVisibleIndex = maxAllowedVisibleChildren - 1; @@ -645,6 +651,11 @@ public class NotificationChildrenContainer extends ViewGroup { mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating ? parentState.zTranslation : 0; + mHeaderViewState.yTranslation = mCurrentHeaderTranslation; + mHeaderViewState.alpha = mHeaderVisibleAmount; + // The hiding is done automatically by the alpha, otherwise we'll pick it up again + // in the next frame with the initFrom call above and have an invisible header + mHeaderViewState.hidden = false; } } @@ -1009,7 +1020,8 @@ public class NotificationChildrenContainer extends ViewGroup { return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true /* likeHighPriority */); } - int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding; + int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding; int visibleChildren = 0; int childCount = mChildren.size(); for (int i = 0; i < childCount; i++) { @@ -1071,7 +1083,8 @@ public class NotificationChildrenContainer extends ViewGroup { } private int getVisibleChildrenExpandHeight() { - int intrinsicHeight = mNotificationHeaderMargin + mNotificatonTopPadding + mDividerHeight; + int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding + mDividerHeight; int visibleChildren = 0; int childCount = mChildren.size(); int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); @@ -1110,7 +1123,7 @@ public class NotificationChildrenContainer extends ViewGroup { if (!likeHighPriority && showingAsLowPriority()) { return mNotificationHeaderLowPriority.getHeight(); } - int minExpandHeight = mNotificationHeaderMargin; + int minExpandHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; int visibleChildren = 0; boolean firstChild = true; int childCount = mChildren.size(); @@ -1190,7 +1203,8 @@ public class NotificationChildrenContainer extends ViewGroup { } public int getPositionInLinearLayout(View childInGroup) { - int position = mNotificationHeaderMargin + mNotificatonTopPadding; + int position = mNotificationHeaderMargin + mCurrentHeaderTranslation + + mNotificatonTopPadding; for (int i = 0; i < mChildren.size(); i++) { ExpandableNotificationRow child = mChildren.get(i); @@ -1281,4 +1295,9 @@ public class NotificationChildrenContainer extends ViewGroup { last = false; } } + + public void setHeaderVisibleAmount(float headerVisibleAmount) { + mHeaderVisibleAmount = headerVisibleAmount; + mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java new file mode 100644 index 000000000000..a36c966a3310 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import android.view.View; + +import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import java.util.HashSet; + +/** + * A class that manages the roundness for notification views + */ +class NotificationRoundnessManager implements OnHeadsUpChangedListener { + + private boolean mExpanded; + private ActivatableNotificationView mFirst; + private ActivatableNotificationView mLast; + private HashSet<View> mAnimatedChildren; + private Runnable mRoundingChangedCallback; + private ExpandableNotificationRow mTrackedHeadsUp; + private float mAppearFraction; + + @Override + public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { + updateRounding(headsUp, false /* animate */); + } + + @Override + public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { + updateRounding(headsUp, true /* animate */); + } + + private void updateRounding(ActivatableNotificationView view, boolean animate) { + float topRoundness = getRoundness(view, true /* top */); + float bottomRoundness = getRoundness(view, false /* top */); + boolean firstChanged = view.setTopRoundness(topRoundness, animate); + boolean secondChanged = view.setBottomRoundness(bottomRoundness, animate); + if ((view == mFirst || view == mLast) && (firstChanged || secondChanged)) { + mRoundingChangedCallback.run(); + } + } + + private float getRoundness(ActivatableNotificationView view, boolean top) { + if ((view.isPinned() || view.isHeadsUpAnimatingAway()) && !mExpanded) { + return 1.0f; + } + if (view == mFirst && top) { + return 1.0f; + } + if (view == mLast && !top) { + return 1.0f; + } + if (view == mTrackedHeadsUp && mAppearFraction <= 0.0f) { + // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be + // rounded. + return 1.0f; + } + return 0.0f; + } + + public void setExpanded(float expandedHeight, float appearFraction) { + mExpanded = expandedHeight != 0.0f; + mAppearFraction = appearFraction; + if (mTrackedHeadsUp != null) { + updateRounding(mTrackedHeadsUp, true); + } + } + + public void setFirstAndLastBackgroundChild(ActivatableNotificationView first, + ActivatableNotificationView last) { + boolean firstChanged = mFirst != first; + boolean lastChanged = mLast != last; + if (!firstChanged && !lastChanged) { + return; + } + ActivatableNotificationView oldFirst = mFirst; + ActivatableNotificationView oldLast = mLast; + mFirst = first; + mLast = last; + if (firstChanged && oldFirst != null && !oldFirst.isRemoved()) { + updateRounding(oldFirst, oldFirst.isShown()); + } + if (lastChanged && oldLast != null && !oldLast.isRemoved()) { + updateRounding(oldLast, oldLast.isShown()); + } + if (mFirst != null) { + updateRounding(mFirst, mFirst.isShown() && !mAnimatedChildren.contains(mFirst)); + } + if (mLast != null) { + updateRounding(mLast, mLast.isShown() && !mAnimatedChildren.contains(mLast)); + } + mRoundingChangedCallback.run(); + } + + public void setAnimatedChildren(HashSet<View> animatedChildren) { + mAnimatedChildren = animatedChildren; + } + + public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) { + mRoundingChangedCallback = roundingChangedCallback; + } + + public void setTrackingHeadsUp(ExpandableNotificationRow row) { + mTrackedHeadsUp = row; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index a85f4e2b53a5..14e801f0b2ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -94,8 +94,8 @@ import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; @@ -108,6 +108,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.function.BiConsumer; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. @@ -289,6 +290,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations = new HashSet<>(); private HeadsUpManagerPhone mHeadsUpManager; + private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager(); private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -403,6 +405,8 @@ public class NotificationStackScrollLayout extends ViewGroup private final Rect mTmpRect = new Rect(); private int mClockBottom; private int mAntiBurnInOffsetX; + private ArrayList<BiConsumer<Float, Float>> mExpandedHeightListeners = new ArrayList<>(); + private int mHeadsUpInset; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -440,6 +444,9 @@ public class NotificationStackScrollLayout extends ViewGroup mSeparatorWidth = res.getDimensionPixelSize(R.dimen.widget_separator_width); mSeparatorThickness = res.getDimensionPixelSize(R.dimen.widget_separator_thickness); mDarkSeparatorPadding = res.getDimensionPixelSize(R.dimen.widget_bottom_separator_padding); + mRoundnessManager.setAnimatedChildren(mChildrenToAddAnimated); + mRoundnessManager.setOnRoundingChangedCallback(this::invalidate); + addOnExpandedHeightListener(mRoundnessManager::setExpanded); updateWillNotDraw(); mBackgroundPaint.setAntiAlias(true); @@ -582,13 +589,15 @@ public class NotificationStackScrollLayout extends ViewGroup res.getDimensionPixelSize(R.dimen.notification_divider_height_increased); mMinTopOverScrollToEscape = res.getDimensionPixelSize( R.dimen.min_top_overscroll_to_qs); - mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height); + mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom); mSidePaddings = res.getDimensionPixelSize(R.dimen.notification_side_paddings); mMinInteractionHeight = res.getDimensionPixelSize( R.dimen.notification_min_interaction_height); mCornerRadius = res.getDimensionPixelSize( Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius)); + mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); } public void setDrawBackgroundAsSrc(boolean asSrc) { @@ -701,7 +710,8 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmLayoutMinHeight() { - mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() ? getLayoutMinHeight() : 0); + mAmbientState.setLayoutMinHeight(mQsExpanded && !onKeyguard() || isHeadsUpTransition() + ? getLayoutMinHeight() : 0); } /** @@ -854,11 +864,12 @@ public class NotificationStackScrollLayout extends ViewGroup float translationY; float appearEndPosition = getAppearEndPosition(); float appearStartPosition = getAppearStartPosition(); + float appearFraction = 1.0f; if (height >= appearEndPosition) { translationY = 0; stackHeight = (int) height; } else { - float appearFraction = getAppearFraction(height); + appearFraction = getAppearFraction(height); if (appearFraction >= 0) { translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0, appearFraction); @@ -867,7 +878,12 @@ public class NotificationStackScrollLayout extends ViewGroup // start translationY = height - appearStartPosition + getExpandTranslationStart(); } - stackHeight = (int) (height - translationY); + if (isHeadsUpTransition()) { + stackHeight = mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); + translationY = MathUtils.lerp(mHeadsUpInset - mTopPadding, 0, appearFraction); + } else { + stackHeight = (int) (height - translationY); + } } if (stackHeight != mCurrentStackHeight) { mCurrentStackHeight = stackHeight; @@ -875,6 +891,10 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } setStackTranslation(translationY); + for (int i = 0; i < mExpandedHeightListeners.size(); i++) { + BiConsumer<Float, Float> listener = mExpandedHeightListeners.get(i); + listener.accept(mExpandedHeight, appearFraction); + } } private void setRequestedClipBounds(Rect clipRect) { @@ -909,12 +929,8 @@ public class NotificationStackScrollLayout extends ViewGroup * Measured in absolute height. */ private float getAppearStartPosition() { - if (mTrackingHeadsUp && mFirstVisibleBackgroundChild != null) { - if (mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild)) { - // If we ever expanded beyond the first notification, it's allowed to merge into - // the shelf - return mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); - } + if (isHeadsUpTransition()) { + return mHeadsUpInset + mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight(); } return getMinExpansionHeight(); } @@ -948,17 +964,14 @@ public class NotificationStackScrollLayout extends ViewGroup int appearPosition; int notGoneChildCount = getNotGoneChildCount(); if (mEmptyShadeView.getVisibility() == GONE && notGoneChildCount != 0) { - int minNotificationsForShelf = 1; - if (mTrackingHeadsUp + if (isHeadsUpTransition() || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { appearPosition = getTopHeadsUpPinnedHeight(); - minNotificationsForShelf = 2; } else { appearPosition = 0; - } - if (notGoneChildCount >= minNotificationsForShelf - && mShelf.getVisibility() != GONE) { - appearPosition += mShelf.getIntrinsicHeight(); + if (notGoneChildCount >= 1 && mShelf.getVisibility() != GONE) { + appearPosition += mShelf.getIntrinsicHeight(); + } } } else { appearPosition = mEmptyShadeView.getHeight(); @@ -966,6 +979,11 @@ public class NotificationStackScrollLayout extends ViewGroup return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding); } + private boolean isHeadsUpTransition() { + return mTrackingHeadsUp && mFirstVisibleBackgroundChild != null + && mAmbientState.isAboveShelf(mFirstVisibleBackgroundChild); + } + /** * @param height the height of the panel * @return the fraction of the appear animation that has been performed @@ -1076,10 +1094,6 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { - if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) { - mScrimController.setTopHeadsUpDragAmount(animView, - Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f)); - } // Returning true prevents alpha fading. return !mFadeNotificationsOnDismiss; } @@ -2521,6 +2535,9 @@ public class NotificationStackScrollLayout extends ViewGroup } public int getLayoutMinHeight() { + if (isHeadsUpTransition()) { + return getTopHeadsUpPinnedHeight(); + } return mShelf.getVisibility() == GONE ? 0 : mShelf.getIntrinsicHeight(); } @@ -2929,42 +2946,18 @@ public class NotificationStackScrollLayout extends ViewGroup private void updateFirstAndLastBackgroundViews() { ActivatableNotificationView firstChild = getFirstChildWithBackground(); ActivatableNotificationView lastChild = getLastChildWithBackground(); - boolean firstChanged = firstChild != mFirstVisibleBackgroundChild; - boolean lastChanged = lastChild != mLastVisibleBackgroundChild; if (mAnimationsEnabled && mIsExpanded) { - mAnimateNextBackgroundTop = firstChanged; - mAnimateNextBackgroundBottom = lastChanged; + mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild; + mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild; } else { mAnimateNextBackgroundTop = false; mAnimateNextBackgroundBottom = false; } - if (firstChanged && mFirstVisibleBackgroundChild != null - && !mFirstVisibleBackgroundChild.isRemoved()) { - mFirstVisibleBackgroundChild.setTopRoundness(0.0f, - mFirstVisibleBackgroundChild.isShown()); - } - if (lastChanged && mLastVisibleBackgroundChild != null - && !mLastVisibleBackgroundChild.isRemoved()) { - mLastVisibleBackgroundChild.setBottomRoundness(0.0f, - mLastVisibleBackgroundChild.isShown()); - } mFirstVisibleBackgroundChild = firstChild; mLastVisibleBackgroundChild = lastChild; mAmbientState.setLastVisibleBackgroundChild(lastChild); - applyRoundedNess(); - } - - private void applyRoundedNess() { - if (mFirstVisibleBackgroundChild != null) { - mFirstVisibleBackgroundChild.setTopRoundness(1.0f, - mFirstVisibleBackgroundChild.isShown() - && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild)); - } - if (mLastVisibleBackgroundChild != null) { - mLastVisibleBackgroundChild.setBottomRoundness(1.0f, - mLastVisibleBackgroundChild.isShown() - && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild)); - } + mRoundnessManager.setFirstAndLastBackgroundChild(mFirstVisibleBackgroundChild, + mLastVisibleBackgroundChild); invalidate(); } @@ -4288,6 +4281,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); + mHeadsUpManager.addListener(mRoundnessManager); } public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) { @@ -4319,8 +4313,9 @@ public class NotificationStackScrollLayout extends ViewGroup requestChildrenUpdate(); } - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; + public void setTrackingHeadsUp(ExpandableNotificationRow row) { + mTrackingHeadsUp = row != null; + mRoundnessManager.setTrackingHeadsUp(row); } public void setScrimController(ScrimController scrimController) { @@ -4503,6 +4498,16 @@ public class NotificationStackScrollLayout extends ViewGroup } /** + * Add a listener whenever the expanded height changes. The first value passed as an argument + * is the expanded height and the second one is the appearFraction. + * + * @param listener the listener to notify. + */ + public void addOnExpandedHeightListener(BiConsumer<Float, Float> listener) { + mExpandedHeightListeners.add(listener); + } + + /** * A listener that is notified when the empty space below the notifications is clicked on */ public interface OnEmptySpaceClickListener { @@ -4880,7 +4885,8 @@ public class NotificationStackScrollLayout extends ViewGroup .animateHeight() .animateTopInset() .animateY() - .animateZ(), + .animateZ() + .hasDelays(), // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK new AnimationFilter() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index 51737a863748..805ce37126eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -50,6 +50,8 @@ public class StackScrollAlgorithm { private boolean mIsExpanded; private boolean mClipNotificationScrollToTop; private int mStatusBarHeight; + private float mHeadsUpInset; + private int mPinnedZTranslationExtra; public StackScrollAlgorithm(Context context) { initView(context); @@ -68,6 +70,10 @@ public class StackScrollAlgorithm { mCollapsedSize = res.getDimensionPixelSize(R.dimen.notification_min_height); mStatusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); mClipNotificationScrollToTop = res.getBoolean(R.bool.config_clipNotificationScrollToTop); + mHeadsUpInset = mStatusBarHeight + res.getDimensionPixelSize( + R.dimen.heads_up_status_bar_padding); + mPinnedZTranslationExtra = res.getDimensionPixelSize( + R.dimen.heads_up_pinned_elevation); } public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { @@ -457,7 +463,7 @@ public class StackScrollAlgorithm { } } if (row.isPinned()) { - childState.yTranslation = Math.max(childState.yTranslation, 0); + childState.yTranslation = Math.max(childState.yTranslation, mHeadsUpInset); childState.height = Math.max(row.getIntrinsicHeight(), childState.height); childState.hidden = false; ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry); @@ -522,9 +528,6 @@ public class StackScrollAlgorithm { childViewState.inShelf = true; childViewState.headsUpIsVisible = false; } - if (!ambientState.isShadeExpanded()) { - childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation); - } } protected int getMaxAllowedChildHeight(View child) { @@ -592,6 +595,13 @@ public class StackScrollAlgorithm { } else { childViewState.zTranslation = baseZ; } + + // We need to scrim the notification more from its surrounding content when we are pinned, + // and we therefore elevate it higher. + // We can use the headerVisibleAmount for this, since the value nicely goes from 0 to 1 when + // expanding after which we have a normal elevation again. + childViewState.zTranslation += (1.0f - child.getHeaderVisibleAmount()) + * mPinnedZTranslationExtra; return childrenOnTop; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index 236c348e539b..d48ae76495f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -29,6 +29,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.StatusBarIconView; import java.util.ArrayList; import java.util.HashSet; @@ -45,13 +46,17 @@ public class StackStateAnimator { public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464; public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150; - public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 650; - public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 230; + public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550; + public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED + = (int) (ANIMATION_DURATION_HEADS_UP_APPEAR + * HeadsUpAppearInterpolator.getFractionUntilOvershoot()); + public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300; public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80; public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32; public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48; public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2; public static final int ANIMATION_DELAY_HEADS_UP = 120; + public static final int ANIMATION_DELAY_HEADS_UP_CLICKED= 120; private final int mGoToFullShadeAppearingTranslation; private final ExpandableViewState mTmpState = new ExpandableViewState(); @@ -74,8 +79,9 @@ public class StackStateAnimator { private ValueAnimator mBottomOverScrollAnimator; private int mHeadsUpAppearHeightBottom; private boolean mShadeExpanded; - private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>(); private NotificationShelf mShelf; + private float mStatusBarIconLocation; + private int[] mTmpLocation = new int[2]; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -222,8 +228,8 @@ public class StackStateAnimator { if (mAnimationFilter.hasGoToFullShadeEvent) { return calculateDelayGoToFullShade(viewState); } - if (mAnimationFilter.hasHeadsUpDisappearClickEvent) { - return ANIMATION_DELAY_HEADS_UP; + if (mAnimationFilter.customDelay != AnimationFilter.NO_DELAY) { + return mAnimationFilter.customDelay; } long minDelay = 0; for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) { @@ -327,10 +333,6 @@ public class StackStateAnimator { private void onAnimationFinished() { mHostLayout.onChildAnimationFinished(); - for (View v : mChildrenToClearFromOverlay) { - removeFromOverlay(v); - } - mChildrenToClearFromOverlay.clear(); } /** @@ -396,13 +398,14 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, - translationDirection, new Runnable() { + 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, + 0, new Runnable() { @Override public void run() { // remove the temporary overlay removeFromOverlay(changingView); } - }); + }, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { // A race condition can trigger the view to be added to the overlay even though @@ -424,7 +427,9 @@ public class StackStateAnimator { if (event.headsUpFromBottom) { mTmpState.yTranslation = mHeadsUpAppearHeightBottom; } else { - mTmpState.yTranslation = -mTmpState.height; + mTmpState.yTranslation = 0; + changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED, + true /* isHeadsUpAppear */); } mHeadsUpAppearChildren.add(changingView); mTmpState.applyToView(changingView); @@ -433,22 +438,56 @@ public class StackStateAnimator { event.animationType == NotificationStackScrollLayout .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) { mHeadsUpDisappearChildren.add(changingView); + Runnable endRunnable = null; + // We need some additional delay in case we were removed to make sure we're not + // lagging + int extraDelay = event.animationType == NotificationStackScrollLayout + .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK + ? ANIMATION_DELAY_HEADS_UP_CLICKED + : 0; if (changingView.getParent() == null) { // This notification was actually removed, so we need to add it to the overlay mHostLayout.getOverlay().add(changingView); mTmpState.initFrom(changingView); - mTmpState.yTranslation = -changingView.getActualHeight(); + mTmpState.yTranslation = 0; // We temporarily enable Y animations, the real filter will be combined // afterwards anyway mAnimationFilter.animateY = true; - mAnimationProperties.delay = - event.animationType == NotificationStackScrollLayout - .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK - ? ANIMATION_DELAY_HEADS_UP - : 0; + mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP; mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR; mTmpState.animateTo(changingView, mAnimationProperties); - mChildrenToClearFromOverlay.add(changingView); + endRunnable = () -> { + // remove the temporary overlay + removeFromOverlay(changingView); + }; + } + float targetLocation = 0; + boolean needsAnimation = true; + if (changingView instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; + if (row.isDismissed()) { + needsAnimation = false; + } + StatusBarIconView icon = row.getEntry().icon; + if (icon.getParent() != null) { + icon.getLocationOnScreen(mTmpLocation); + float iconPosition = mTmpLocation[0] - icon.getTranslationX() + + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f; + mHostLayout.getLocationOnScreen(mTmpLocation); + targetLocation = iconPosition - mTmpLocation[0]; + } + } + + if (needsAnimation) { + // We need to add the global animation listener, since once no animations are + // running anymore, the panel will instantly hide itself. We need to wait until + // the animation is fully finished for this though. + changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR + + ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f, + true /* isHeadsUpAppear */, targetLocation, endRunnable, + getGlobalAnimationFinishedListener()); + } else if (endRunnable != null) { + endRunnable.run(); } } mNewEvents.add(event); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 04a7bd79c6ca..4b3643f620a0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -641,6 +641,22 @@ public class ViewState { } /** + * Get the end value of the xTranslation animation running on a view or the xTranslation + * if no animation is running. + */ + public static float getFinalTranslationX(View view) { + if (view == null) { + return 0; + } + ValueAnimator xAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_X); + if (xAnimator == null) { + return view.getTranslationX(); + } else { + return getChildTag(view, TAG_END_TRANSLATION_X); + } + } + + /** * Get the end value of the yTranslation animation running on a view or the yTranslation * if no animation is running. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java new file mode 100644 index 000000000000..c904ef30dd16 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.widget.TextView; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.TestableDependency; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.HeadsUpStatusBarView; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.policy.DarkIconDispatcher; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HeadsUpAppearanceControllerTest extends SysuiTestCase { + + private HeadsUpAppearanceController mHeadsUpAppearanceController; + private ExpandableNotificationRow mFirst; + private HeadsUpStatusBarView mHeadsUpStatusBarView; + private HeadsUpManagerPhone mHeadsUpManager; + + @Before + public void setUp() throws Exception { + NotificationTestHelper testHelper = new NotificationTestHelper(getContext()); + mFirst = testHelper.createRow(); + mDependency.injectMockDependency(DarkIconDispatcher.class); + mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class), + mock(TextView.class)); + mHeadsUpManager = mock(HeadsUpManagerPhone.class); + mHeadsUpAppearanceController = new HeadsUpAppearanceController( + mock(NotificationIconAreaController.class), + mHeadsUpManager, + mHeadsUpStatusBarView, + mock(NotificationStackScrollLayout.class), + mock(NotificationPanelView.class), + new View(mContext)); + mHeadsUpAppearanceController.setExpandedHeight(0.0f, 0.0f); + } + + @Test + public void testShowinEntryUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertEquals(mFirst.getEntry(), mHeadsUpStatusBarView.getShowingEntry()); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertEquals(null, mHeadsUpStatusBarView.getShowingEntry()); + } + + @Test + public void testShownUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertTrue(mHeadsUpAppearanceController.isShown()); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertFalse(mHeadsUpAppearanceController.isShown()); + } + + @Test + public void testHeaderUpdated() { + mFirst.setPinned(true); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true); + when(mHeadsUpManager.getTopEntry()).thenReturn(mFirst.getEntry()); + mHeadsUpAppearanceController.onHeadsUpPinned(mFirst); + Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 0.0f, 0.0f); + + mFirst.setPinned(false); + when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false); + mHeadsUpAppearanceController.onHeadsUpUnPinned(mFirst); + Assert.assertEquals(mFirst.getHeaderVisibleAmount(), 1.0f, 0.0f); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index d32c9a8e5649..72d9cc83627a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -64,7 +64,6 @@ public class ScrimControllerTest extends SysuiTestCase { private SynchronousScrimController mScrimController; private ScrimView mScrimBehind; private ScrimView mScrimInFront; - private View mHeadsUpScrim; private Consumer<Integer> mScrimVisibilityCallback; private int mScrimVisibility; private LightBarController mLightBarController; @@ -78,7 +77,6 @@ public class ScrimControllerTest extends SysuiTestCase { mLightBarController = mock(LightBarController.class); mScrimBehind = new ScrimView(getContext()); mScrimInFront = new ScrimView(getContext()); - mHeadsUpScrim = new View(getContext()); mWakeLock = mock(WakeLock.class); mAlarmManager = mock(AlarmManager.class); mAlwaysOnEnabled = true; @@ -87,7 +85,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled); when(mDozeParamenters.getDisplayNeedsBlanking()).thenReturn(true); mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind, - mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters, + mScrimInFront, mScrimVisibilityCallback, mDozeParamenters, mAlarmManager); } @@ -415,56 +413,6 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void testHeadsUpScrimOpacity() { - mScrimController.setPanelExpansion(0f); - mScrimController.onHeadsUpPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - - Assert.assertNotEquals("Heads-up scrim should be visible", 0f, - mHeadsUpScrim.getAlpha(), 0.01f); - - mScrimController.onHeadsUpUnPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - - Assert.assertEquals("Heads-up scrim should have disappeared", 0f, - mHeadsUpScrim.getAlpha(), 0.01f); - } - - @Test - public void testHeadsUpScrimCounting() { - mScrimController.setPanelExpansion(0f); - mScrimController.onHeadsUpPinned(null /* row */); - mScrimController.onHeadsUpPinned(null /* row */); - mScrimController.onHeadsUpPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - - Assert.assertNotEquals("Heads-up scrim should be visible", 0f, - mHeadsUpScrim.getAlpha(), 0.01f); - - mScrimController.onHeadsUpUnPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - - Assert.assertEquals("Heads-up scrim should only disappear when counter reaches 0", 1f, - mHeadsUpScrim.getAlpha(), 0.01f); - - mScrimController.onHeadsUpUnPinned(null /* row */); - mScrimController.onHeadsUpUnPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - Assert.assertEquals("Heads-up scrim should have disappeared", 0f, - mHeadsUpScrim.getAlpha(), 0.01f); - } - - @Test - public void testNoHeadsUpScrimExpanded() { - mScrimController.setPanelExpansion(1f); - mScrimController.onHeadsUpPinned(null /* row */); - mScrimController.finishAnimationsImmediately(); - - Assert.assertEquals("Heads-up scrim should not be visible when shade is expanded", 0f, - mHeadsUpScrim.getAlpha(), 0.01f); - } - - @Test public void testScrimFocus() { mScrimController.transitionTo(ScrimState.AOD); Assert.assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable()); @@ -542,10 +490,10 @@ public class ScrimControllerTest extends SysuiTestCase { private boolean mAnimationCancelled; SynchronousScrimController(LightBarController lightBarController, - ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, + ScrimView scrimBehind, ScrimView scrimInFront, Consumer<Integer> scrimVisibleListener, DozeParameters dozeParameters, AlarmManager alarmManager) { - super(lightBarController, scrimBehind, scrimInFront, headsUpScrim, + super(lightBarController, scrimBehind, scrimInFront, scrimVisibleListener, dozeParameters, alarmManager); mHandler = new FakeHandler(Looper.myLooper()); } @@ -562,7 +510,6 @@ public class ScrimControllerTest extends SysuiTestCase { // Force finish all animations. endAnimation(mScrimBehind, TAG_KEY_ANIM); endAnimation(mScrimInFront, TAG_KEY_ANIM); - endAnimation(mHeadsUpScrim, TAG_KEY_ANIM); if (!animationFinished[0]) { throw new IllegalStateException("Animation never finished"); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java new file mode 100644 index 000000000000..1d2c01dbd564 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.stack; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationTestHelper; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.phone.ScrimController; +import com.android.systemui.statusbar.phone.StatusBar; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.HashSet; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class NotificationRoundnessManagerTest extends SysuiTestCase { + + private NotificationRoundnessManager mRoundnessManager = new NotificationRoundnessManager(); + private HashSet<View> mAnimatedChildren = new HashSet<>(); + private Runnable mRoundnessCallback = mock(Runnable.class); + private ExpandableNotificationRow mFirst; + private ExpandableNotificationRow mSecond; + + @Before + public void setUp() throws Exception { + NotificationTestHelper testHelper = new NotificationTestHelper(getContext()); + mFirst = testHelper.createRow(); + mSecond = testHelper.createRow(); + mRoundnessManager.setOnRoundingChangedCallback(mRoundnessCallback); + mRoundnessManager.setAnimatedChildren(mAnimatedChildren); + mRoundnessManager.setFirstAndLastBackgroundChild(mFirst, mFirst); + mRoundnessManager.setExpanded(1.0f, 1.0f); + } + + @Test + public void testCallbackCalledWhenSecondChanged() { + mRoundnessManager.setFirstAndLastBackgroundChild(mFirst, mSecond); + verify(mRoundnessCallback, atLeast(1)).run(); + } + + @Test + public void testCallbackCalledWhenFirstChanged() { + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mFirst); + verify(mRoundnessCallback, atLeast(1)).run(); + } + + @Test + public void testRoundnessSetOnLast() { + mRoundnessManager.setFirstAndLastBackgroundChild(mFirst, mSecond); + Assert.assertEquals(1.0f, mSecond.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(0.0f, mSecond.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testRoundnessSetOnNew() { + mRoundnessManager.setFirstAndLastBackgroundChild(mFirst, null); + Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testCompleteReplacement() { + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(0.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testNotCalledWhenRemoved() { + mFirst.setRemoved(); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testRoundedWhenPinnedAndCollapsed() { + mFirst.setPinned(true); + mRoundnessManager.setExpanded(0.0f /* expandedHeight */, 0.0f /* appearFraction */); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testRoundedWhenGoingAwayAndCollapsed() { + mFirst.setHeadsUpAnimatingAway(true); + mRoundnessManager.setExpanded(0.0f /* expandedHeight */, 0.0f /* appearFraction */); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testRoundedNormalRoundingWhenExpanded() { + mFirst.setHeadsUpAnimatingAway(true); + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, 0.0f /* appearFraction */); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(0.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testTrackingHeadsUpRoundedIfPushingUp() { + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, -0.5f /* appearFraction */); + mRoundnessManager.setTrackingHeadsUp(mFirst); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(1.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(1.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } + + @Test + public void testTrackingHeadsUpNotRoundedIfPushingDown() { + mRoundnessManager.setExpanded(1.0f /* expandedHeight */, 0.5f /* appearFraction */); + mRoundnessManager.setTrackingHeadsUp(mFirst); + mRoundnessManager.setFirstAndLastBackgroundChild(mSecond, mSecond); + Assert.assertEquals(0.0f, mFirst.getCurrentBottomRoundness(), 0.0f); + Assert.assertEquals(0.0f, mFirst.getCurrentTopRoundness(), 0.0f); + } +} |