summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java45
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--packages/SystemUI/res/drawable/heads_up_scrim.xml25
-rw-r--r--packages/SystemUI/res/layout/heads_up_status_bar_layout.xml48
-rw-r--r--packages/SystemUI/res/layout/notification_icon_area.xml6
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml72
-rw-r--r--packages/SystemUI/res/layout/super_status_bar.xml8
-rw-r--r--packages/SystemUI/res/values/dimens.xml7
-rw-r--r--packages/SystemUI/res/values/styles.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/HeadsUpStatusBarView.java193
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationHeaderViewWrapper.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java225
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/HeadsUpAppearInterpolator.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationRoundnessManager.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationRoundnessManagerTest.java156
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);
+ }
+}